[{"data":1,"prerenderedAt":2028},["ShallowReactive",2],{"article-/topics/design/complex-interaction-component-guide":3,"related-design":609,"content-query-MHaBxgfHpW":1549},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"image":11,"head":12,"body":17,"_type":603,"_id":604,"_source":605,"_file":606,"_stem":607,"_extension":608},"/topics/design/complex-interaction-component-guide","design",false,"","复杂交互组件设计与实现方案","深入解析拖拽排序、多级选择器、可视化编辑器等复杂交互组件的设计原理与技术实现","2026-01-01","/images/topics/design/complex-interaction-component.jpg",{"meta":13},[14],{"name":15,"content":16},"keywords","复杂交互组件,拖拽排序,多级选择器,可视化编辑器,树形组件,组件设计",{"type":18,"children":19,"toc":577},"root",[20,27,33,38,43,166,171,176,182,195,200,211,217,222,231,237,242,247,256,261,270,275,280,285,294,299,304,313,318,323,332,337,342,347,356,361,370,375,380,497,506,511,516,572],{"type":21,"tag":22,"props":23,"children":24},"element","h2",{"id":8},[25],{"type":26,"value":8},"text",{"type":21,"tag":28,"props":29,"children":30},"p",{},[31],{"type":26,"value":32},"在现代 Web 应用中，复杂交互组件是提升用户体验的关键。本文将深入讲解拖拽排序、多级选择器、可视化编辑器等复杂交互组件的设计思路与实现方案。",{"type":21,"tag":22,"props":34,"children":36},{"id":35},"复杂交互组件概述",[37],{"type":26,"value":35},{"type":21,"tag":28,"props":39,"children":40},{},[41],{"type":26,"value":42},"复杂交互组件通常具有以下特征：",{"type":21,"tag":44,"props":45,"children":46},"table",{},[47,71],{"type":21,"tag":48,"props":49,"children":50},"thead",{},[51],{"type":21,"tag":52,"props":53,"children":54},"tr",{},[55,61,66],{"type":21,"tag":56,"props":57,"children":58},"th",{},[59],{"type":26,"value":60},"特征",{"type":21,"tag":56,"props":62,"children":63},{},[64],{"type":26,"value":65},"说明",{"type":21,"tag":56,"props":67,"children":68},{},[69],{"type":26,"value":70},"典型组件",{"type":21,"tag":72,"props":73,"children":74},"tbody",{},[75,94,112,130,148],{"type":21,"tag":52,"props":76,"children":77},{},[78,84,89],{"type":21,"tag":79,"props":80,"children":81},"td",{},[82],{"type":26,"value":83},"多步骤操作",{"type":21,"tag":79,"props":85,"children":86},{},[87],{"type":26,"value":88},"需要连续的用户动作",{"type":21,"tag":79,"props":90,"children":91},{},[92],{"type":26,"value":93},"拖拽排序、手势操作",{"type":21,"tag":52,"props":95,"children":96},{},[97,102,107],{"type":21,"tag":79,"props":98,"children":99},{},[100],{"type":26,"value":101},"嵌套数据结构",{"type":21,"tag":79,"props":103,"children":104},{},[105],{"type":26,"value":106},"处理层级化数据",{"type":21,"tag":79,"props":108,"children":109},{},[110],{"type":26,"value":111},"树形控件、级联选择",{"type":21,"tag":52,"props":113,"children":114},{},[115,120,125],{"type":21,"tag":79,"props":116,"children":117},{},[118],{"type":26,"value":119},"实时反馈",{"type":21,"tag":79,"props":121,"children":122},{},[123],{"type":26,"value":124},"操作过程中持续更新",{"type":21,"tag":79,"props":126,"children":127},{},[128],{"type":26,"value":129},"滑块、颜色选择器",{"type":21,"tag":52,"props":131,"children":132},{},[133,138,143],{"type":21,"tag":79,"props":134,"children":135},{},[136],{"type":26,"value":137},"状态复杂",{"type":21,"tag":79,"props":139,"children":140},{},[141],{"type":26,"value":142},"多种状态转换",{"type":21,"tag":79,"props":144,"children":145},{},[146],{"type":26,"value":147},"可展开表格、穿梭框",{"type":21,"tag":52,"props":149,"children":150},{},[151,156,161],{"type":21,"tag":79,"props":152,"children":153},{},[154],{"type":26,"value":155},"多元素协作",{"type":21,"tag":79,"props":157,"children":158},{},[159],{"type":26,"value":160},"多个元素配合工作",{"type":21,"tag":79,"props":162,"children":163},{},[164],{"type":26,"value":165},"分栏布局、面板组",{"type":21,"tag":22,"props":167,"children":169},{"id":168},"拖拽排序组件",[170],{"type":26,"value":168},{"type":21,"tag":28,"props":172,"children":173},{},[174],{"type":26,"value":175},"拖拽排序是最常见的复杂交互之一，广泛应用于任务管理、列表排序等场景。",{"type":21,"tag":177,"props":178,"children":180},"h3",{"id":179},"基础拖拽实现",[181],{"type":26,"value":179},{"type":21,"tag":183,"props":184,"children":189},"pre",{"className":185,"code":187,"language":188,"meta":7},[186],"language-typescript","// 拖拽排序的核心状态管理\ninterface DragState {\n  isDragging: boolean\n  dragIndex: number | null\n  hoverIndex: number | null\n  dragOffset: { x: number; y: number }\n}\n\nfunction useDragSort\u003CT>(items: Ref\u003CT[]>, options: DragOptions = {}) {\n  const state = reactive\u003CDragState>({\n    isDragging: false,\n    dragIndex: null,\n    hoverIndex: null,\n    dragOffset: { x: 0, y: 0 }\n  })\n  \n  // 开始拖拽\n  function handleDragStart(index: number, event: DragEvent) {\n    state.isDragging = true\n    state.dragIndex = index\n    \n    // 设置拖拽图像\n    if (event.dataTransfer) {\n      event.dataTransfer.effectAllowed = 'move'\n      event.dataTransfer.setData('text/plain', String(index))\n      \n      // 自定义拖拽预览\n      if (options.customPreview) {\n        const preview = createDragPreview(items.value[index])\n        event.dataTransfer.setDragImage(preview, 0, 0)\n      }\n    }\n    \n    options.onDragStart?.(index, items.value[index])\n  }\n  \n  // 拖拽经过\n  function handleDragOver(index: number, event: DragEvent) {\n    event.preventDefault()\n    \n    if (state.dragIndex === null || state.dragIndex === index) return\n    \n    // 计算插入位置（上半部分还是下半部分）\n    const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n    const midY = rect.top + rect.height / 2\n    const insertAfter = event.clientY > midY\n    \n    state.hoverIndex = insertAfter ? index : index - 1\n  }\n  \n  // 放置元素\n  function handleDrop(event: DragEvent) {\n    event.preventDefault()\n    \n    if (state.dragIndex === null || state.hoverIndex === null) return\n    \n    // 执行排序\n    const newItems = [...items.value]\n    const [draggedItem] = newItems.splice(state.dragIndex, 1)\n    \n    const insertIndex = state.hoverIndex >= state.dragIndex \n      ? state.hoverIndex \n      : state.hoverIndex + 1\n    \n    newItems.splice(insertIndex, 0, draggedItem)\n    items.value = newItems\n    \n    options.onDrop?.(state.dragIndex, insertIndex, draggedItem)\n    resetState()\n  }\n  \n  function resetState() {\n    state.isDragging = false\n    state.dragIndex = null\n    state.hoverIndex = null\n  }\n  \n  return {\n    state,\n    handleDragStart,\n    handleDragOver,\n    handleDrop,\n    handleDragEnd: resetState\n  }\n}\n","typescript",[190],{"type":21,"tag":191,"props":192,"children":193},"code",{"__ignoreMap":7},[194],{"type":26,"value":187},{"type":21,"tag":177,"props":196,"children":198},{"id":197},"拖拽排序组件实现",[199],{"type":26,"value":197},{"type":21,"tag":183,"props":201,"children":206},{"className":202,"code":204,"language":205,"meta":7},[203],"language-vue","\u003Ctemplate>\n  \u003Cdiv class=\"sortable-list\" :class=\"{ 'is-dragging': state.isDragging }\">\n    \u003CTransitionGroup name=\"sortable\" tag=\"div\">\n      \u003Cdiv\n        v-for=\"(item, index) in items\"\n        :key=\"getItemKey(item)\"\n        class=\"sortable-item\"\n        :class=\"{\n          'is-dragging': state.dragIndex === index,\n          'is-hover-above': state.hoverIndex === index - 1,\n          'is-hover-below': state.hoverIndex === index\n        }\"\n        :draggable=\"!disabled\"\n        @dragstart=\"handleDragStart(index, $event)\"\n        @dragover=\"handleDragOver(index, $event)\"\n        @dragend=\"handleDragEnd\"\n        @drop=\"handleDrop\"\n      >\n        \u003C!-- 拖拽手柄 -->\n        \u003Cdiv v-if=\"showHandle\" class=\"sortable-handle\">\n          \u003CIcon name=\"grip-vertical\" />\n        \u003C/div>\n        \n        \u003C!-- 内容插槽 -->\n        \u003Cdiv class=\"sortable-content\">\n          \u003Cslot :item=\"item\" :index=\"index\" />\n        \u003C/div>\n      \u003C/div>\n    \u003C/TransitionGroup>\n    \n    \u003C!-- 拖拽占位符 -->\n    \u003Cdiv \n      v-if=\"state.isDragging\" \n      class=\"sortable-placeholder\"\n      :style=\"placeholderStyle\"\n    />\n  \u003C/div>\n\u003C/template>\n\n\u003Cstyle scoped>\n.sortable-list {\n  position: relative;\n}\n\n.sortable-item {\n  display: flex;\n  align-items: center;\n  padding: var(--spacing-sm) var(--spacing-md);\n  background: var(--color-bg-primary);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  margin-bottom: var(--spacing-sm);\n  cursor: grab;\n  transition: transform 0.2s, box-shadow 0.2s;\n}\n\n.sortable-item.is-dragging {\n  opacity: 0.5;\n  cursor: grabbing;\n}\n\n.sortable-item.is-hover-above::before,\n.sortable-item.is-hover-below::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  height: 2px;\n  background: var(--color-primary);\n}\n\n.sortable-item.is-hover-above::before {\n  top: -5px;\n}\n\n.sortable-item.is-hover-below::after {\n  bottom: -5px;\n}\n\n.sortable-handle {\n  cursor: grab;\n  padding: var(--spacing-xs);\n  color: var(--color-text-tertiary);\n}\n\n/* 过渡动画 */\n.sortable-move {\n  transition: transform 0.3s;\n}\n\n.sortable-enter-active,\n.sortable-leave-active {\n  transition: all 0.3s;\n}\n\n.sortable-enter-from,\n.sortable-leave-to {\n  opacity: 0;\n  transform: translateX(-30px);\n}\n\u003C/style>\n","vue",[207],{"type":21,"tag":191,"props":208,"children":209},{"__ignoreMap":7},[210],{"type":26,"value":204},{"type":21,"tag":177,"props":212,"children":214},{"id":213},"看板拖拽跨列拖拽",[215],{"type":26,"value":216},"看板拖拽（跨列拖拽）",{"type":21,"tag":28,"props":218,"children":219},{},[220],{"type":26,"value":221},"看板式拖拽需要处理跨容器的元素移动：",{"type":21,"tag":183,"props":223,"children":226},{"className":224,"code":225,"language":188,"meta":7},[186],"interface KanbanState {\n  dragItem: KanbanItem | null\n  sourceColumnId: string | null\n  targetColumnId: string | null\n  hoverIndex: number\n}\n\nfunction useKanbanDrag(columns: Ref\u003CKanbanColumn[]>) {\n  const state = reactive\u003CKanbanState>({\n    dragItem: null,\n    sourceColumnId: null,\n    targetColumnId: null,\n    hoverIndex: -1\n  })\n  \n  function handleCardDragStart(item: KanbanItem, columnId: string) {\n    state.dragItem = item\n    state.sourceColumnId = columnId\n  }\n  \n  function handleColumnDragOver(columnId: string, index: number) {\n    state.targetColumnId = columnId\n    state.hoverIndex = index\n  }\n  \n  function handleCardDrop() {\n    if (!state.dragItem || !state.sourceColumnId || !state.targetColumnId) return\n    \n    const sourceColumn = columns.value.find(c => c.id === state.sourceColumnId)\n    const targetColumn = columns.value.find(c => c.id === state.targetColumnId)\n    \n    if (!sourceColumn || !targetColumn) return\n    \n    // 从源列移除\n    const itemIndex = sourceColumn.items.findIndex(i => i.id === state.dragItem!.id)\n    if (itemIndex > -1) {\n      sourceColumn.items.splice(itemIndex, 1)\n    }\n    \n    // 插入目标列\n    targetColumn.items.splice(state.hoverIndex, 0, state.dragItem)\n    \n    // 如果跨列移动，可能需要更新卡片状态\n    if (state.sourceColumnId !== state.targetColumnId) {\n      state.dragItem.status = targetColumn.status\n    }\n    \n    resetState()\n  }\n  \n  return { state, handleCardDragStart, handleColumnDragOver, handleCardDrop }\n}\n",[227],{"type":21,"tag":191,"props":228,"children":229},{"__ignoreMap":7},[230],{"type":26,"value":225},{"type":21,"tag":22,"props":232,"children":234},{"id":233},"多级选择器级联选择",[235],{"type":26,"value":236},"多级选择器（级联选择）",{"type":21,"tag":28,"props":238,"children":239},{},[240],{"type":26,"value":241},"级联选择器用于处理树形数据的选择，如地区选择、分类选择等。",{"type":21,"tag":177,"props":243,"children":245},{"id":244},"级联数据结构",[246],{"type":26,"value":244},{"type":21,"tag":183,"props":248,"children":251},{"className":249,"code":250,"language":188,"meta":7},[186],"interface CascadeOption {\n  value: string | number\n  label: string\n  children?: CascadeOption[]\n  disabled?: boolean\n  isLeaf?: boolean     // 标记是否为叶子节点\n  loading?: boolean    // 异步加载状态\n}\n\ninterface CascadeProps {\n  options: CascadeOption[]\n  value: (string | number)[]\n  multiple?: boolean\n  checkStrictly?: boolean  // 是否可以选择任意一级\n  lazy?: boolean           // 是否开启懒加载\n  loadData?: (node: CascadeOption) => Promise\u003CCascadeOption[]>\n}\n",[252],{"type":21,"tag":191,"props":253,"children":254},{"__ignoreMap":7},[255],{"type":26,"value":250},{"type":21,"tag":177,"props":257,"children":259},{"id":258},"级联选择器实现",[260],{"type":26,"value":258},{"type":21,"tag":183,"props":262,"children":265},{"className":263,"code":264,"language":205,"meta":7},[203],"\u003Ctemplate>\n  \u003Cdiv class=\"cascader\" :class=\"{ 'is-focus': isOpen }\">\n    \u003C!-- 触发器 -->\n    \u003Cdiv class=\"cascader-trigger\" @click=\"toggleDropdown\">\n      \u003Cdiv class=\"cascader-tags\" v-if=\"multiple && selectedLabels.length\">\n        \u003CTag \n          v-for=\"(label, index) in selectedLabels.slice(0, maxTagCount)\" \n          :key=\"index\"\n          closable\n          @close=\"removeSelection(index)\"\n        >\n          {{ label }}\n        \u003C/Tag>\n        \u003CTag v-if=\"selectedLabels.length > maxTagCount\">\n          +{{ selectedLabels.length - maxTagCount }}\n        \u003C/Tag>\n      \u003C/div>\n      \u003Cspan v-else class=\"cascader-label\">\n        {{ displayLabel || placeholder }}\n      \u003C/span>\n      \u003CIcon :name=\"isOpen ? 'chevron-up' : 'chevron-down'\" class=\"cascader-arrow\" />\n    \u003C/div>\n    \n    \u003C!-- 下拉面板 -->\n    \u003CTeleport to=\"body\">\n      \u003CTransition name=\"dropdown\">\n        \u003Cdiv v-if=\"isOpen\" class=\"cascader-dropdown\" :style=\"dropdownStyle\">\n          \u003C!-- 多列选择面板 -->\n          \u003Cdiv class=\"cascader-panels\">\n            \u003Cdiv \n              v-for=\"(column, level) in activePanels\" \n              :key=\"level\"\n              class=\"cascader-panel\"\n            >\n              \u003Cdiv class=\"panel-search\" v-if=\"filterable && level === 0\">\n                \u003CInput \n                  v-model=\"searchQuery\" \n                  placeholder=\"搜索...\"\n                  prefix-icon=\"search\"\n                />\n              \u003C/div>\n              \u003Cul class=\"panel-list\">\n                \u003Cli\n                  v-for=\"option in column\"\n                  :key=\"option.value\"\n                  class=\"panel-item\"\n                  :class=\"{\n                    'is-active': isOptionSelected(option, level),\n                    'is-disabled': option.disabled,\n                    'is-loading': option.loading\n                  }\"\n                  @click=\"handleOptionClick(option, level)\"\n                >\n                  \u003CCheckbox \n                    v-if=\"multiple\"\n                    :checked=\"isOptionChecked(option)\"\n                    :indeterminate=\"isOptionIndeterminate(option)\"\n                    @click.stop\n                    @change=\"handleCheckChange(option)\"\n                  />\n                  \u003Cspan class=\"item-label\">{{ option.label }}\u003C/span>\n                  \u003CSpin v-if=\"option.loading\" size=\"small\" />\n                  \u003CIcon \n                    v-else-if=\"option.children?.length || (!option.isLeaf && lazy)\" \n                    name=\"chevron-right\" \n                    class=\"item-arrow\" \n                  />\n                \u003C/li>\n              \u003C/ul>\n            \u003C/div>\n          \u003C/div>\n        \u003C/div>\n      \u003C/Transition>\n    \u003C/Teleport>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nconst props = defineProps\u003CCascadeProps>()\nconst emit = defineEmits(['update:value', 'change'])\n\n// 当前展开的路径\nconst expandedPath = ref\u003C(string | number)[]>([])\n\n// 计算当前显示的面板列\nconst activePanels = computed(() => {\n  const panels: CascadeOption[][] = [props.options]\n  \n  let currentOptions = props.options\n  for (const value of expandedPath.value) {\n    const selected = currentOptions.find(opt => opt.value === value)\n    if (selected?.children?.length) {\n      panels.push(selected.children)\n      currentOptions = selected.children\n    } else {\n      break\n    }\n  }\n  \n  return panels\n})\n\n// 选项点击处理\nasync function handleOptionClick(option: CascadeOption, level: number) {\n  if (option.disabled) return\n  \n  // 更新展开路径\n  expandedPath.value = expandedPath.value.slice(0, level)\n  expandedPath.value.push(option.value)\n  \n  // 懒加载子节点\n  if (props.lazy && !option.isLeaf && !option.children) {\n    option.loading = true\n    try {\n      const children = await props.loadData?.(option)\n      option.children = children\n    } finally {\n      option.loading = false\n    }\n    return\n  }\n  \n  // 判断是否可以选择\n  const canSelect = props.checkStrictly || option.isLeaf || !option.children?.length\n  if (canSelect && !props.multiple) {\n    emit('update:value', [...expandedPath.value])\n    emit('change', expandedPath.value, getSelectedOptions())\n    isOpen.value = false\n  }\n}\n\n// 获取显示标签\nconst displayLabel = computed(() => {\n  if (!props.value?.length) return ''\n  \n  const labels: string[] = []\n  let options = props.options\n  \n  for (const value of props.value) {\n    const option = options.find(opt => opt.value === value)\n    if (option) {\n      labels.push(option.label)\n      options = option.children || []\n    }\n  }\n  \n  return labels.join(' / ')\n})\n\u003C/script>\n",[266],{"type":21,"tag":191,"props":267,"children":268},{"__ignoreMap":7},[269],{"type":26,"value":264},{"type":21,"tag":22,"props":271,"children":273},{"id":272},"树形组件",[274],{"type":26,"value":272},{"type":21,"tag":28,"props":276,"children":277},{},[278],{"type":26,"value":279},"树形组件用于展示和操作层级数据。",{"type":21,"tag":177,"props":281,"children":283},{"id":282},"树节点渲染",[284],{"type":26,"value":282},{"type":21,"tag":183,"props":286,"children":289},{"className":287,"code":288,"language":205,"meta":7},[203],"\u003Ctemplate>\n  \u003Cdiv class=\"tree\">\n    \u003CTreeNode\n      v-for=\"node in treeData\"\n      :key=\"node.id\"\n      :node=\"node\"\n      :level=\"0\"\n      :expanded-keys=\"expandedKeys\"\n      :selected-keys=\"selectedKeys\"\n      :checked-keys=\"checkedKeys\"\n      :checkable=\"checkable\"\n      :selectable=\"selectable\"\n      :draggable=\"draggable\"\n      @expand=\"handleExpand\"\n      @select=\"handleSelect\"\n      @check=\"handleCheck\"\n    />\n  \u003C/div>\n\u003C/template>\n\n\u003C!-- TreeNode.vue -->\n\u003Ctemplate>\n  \u003Cdiv class=\"tree-node\" :style=\"{ paddingLeft: `${level * indent}px` }\">\n    \u003Cdiv \n      class=\"tree-node-content\"\n      :class=\"{\n        'is-selected': isSelected,\n        'is-disabled': node.disabled\n      }\"\n      @click=\"handleClick\"\n    >\n      \u003C!-- 展开/折叠图标 -->\n      \u003Cspan class=\"tree-node-switcher\" @click.stop=\"handleExpand\">\n        \u003CIcon \n          v-if=\"hasChildren\" \n          :name=\"isExpanded ? 'caret-down' : 'caret-right'\" \n        />\n        \u003Cspan v-else class=\"switcher-placeholder\" />\n      \u003C/span>\n      \n      \u003C!-- 复选框 -->\n      \u003CCheckbox\n        v-if=\"checkable\"\n        :checked=\"isChecked\"\n        :indeterminate=\"isIndeterminate\"\n        :disabled=\"node.disabled\"\n        @change=\"handleCheck\"\n        @click.stop\n      />\n      \n      \u003C!-- 节点图标 -->\n      \u003Cspan v-if=\"node.icon\" class=\"tree-node-icon\">\n        \u003CIcon :name=\"node.icon\" />\n      \u003C/span>\n      \n      \u003C!-- 节点标题 -->\n      \u003Cspan class=\"tree-node-title\">\n        \u003Cslot :node=\"node\">{{ node.title }}\u003C/slot>\n      \u003C/span>\n    \u003C/div>\n    \n    \u003C!-- 子节点 -->\n    \u003CTransition name=\"tree-expand\">\n      \u003Cdiv v-if=\"isExpanded && hasChildren\" class=\"tree-node-children\">\n        \u003CTreeNode\n          v-for=\"child in node.children\"\n          :key=\"child.id\"\n          :node=\"child\"\n          :level=\"level + 1\"\n          v-bind=\"$attrs\"\n        />\n      \u003C/div>\n    \u003C/Transition>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nconst props = defineProps\u003C{\n  node: TreeNodeData\n  level: number\n  expandedKeys: Set\u003Cstring>\n  selectedKeys: Set\u003Cstring>\n  checkedKeys: Set\u003Cstring>\n  checkable: boolean\n  selectable: boolean\n}>()\n\nconst emit = defineEmits(['expand', 'select', 'check'])\n\nconst hasChildren = computed(() => props.node.children?.length > 0)\nconst isExpanded = computed(() => props.expandedKeys.has(props.node.id))\nconst isSelected = computed(() => props.selectedKeys.has(props.node.id))\nconst isChecked = computed(() => props.checkedKeys.has(props.node.id))\n\n// 计算半选状态\nconst isIndeterminate = computed(() => {\n  if (!hasChildren.value) return false\n  \n  const children = getAllDescendants(props.node)\n  const checkedCount = children.filter(c => props.checkedKeys.has(c.id)).length\n  \n  return checkedCount > 0 && checkedCount \u003C children.length\n})\n\u003C/script>\n",[290],{"type":21,"tag":191,"props":291,"children":292},{"__ignoreMap":7},[293],{"type":26,"value":288},{"type":21,"tag":177,"props":295,"children":297},{"id":296},"树形复选逻辑",[298],{"type":26,"value":296},{"type":21,"tag":28,"props":300,"children":301},{},[302],{"type":26,"value":303},"树形组件的复选需要处理父子联动：",{"type":21,"tag":183,"props":305,"children":308},{"className":306,"code":307,"language":188,"meta":7},[186],"function useTreeCheck(treeData: Ref\u003CTreeNodeData[]>) {\n  const checkedKeys = ref\u003CSet\u003Cstring>>(new Set())\n  \n  function check(node: TreeNodeData, checked: boolean) {\n    if (checked) {\n      // 选中：同时选中所有子节点\n      checkedKeys.value.add(node.id)\n      getAllDescendants(node).forEach(child => {\n        checkedKeys.value.add(child.id)\n      })\n    } else {\n      // 取消选中：同时取消所有子节点\n      checkedKeys.value.delete(node.id)\n      getAllDescendants(node).forEach(child => {\n        checkedKeys.value.delete(child.id)\n      })\n    }\n    \n    // 更新所有祖先节点的选中状态\n    updateAncestorsCheckState(node)\n  }\n  \n  function updateAncestorsCheckState(node: TreeNodeData) {\n    const parent = findParentNode(treeData.value, node.id)\n    if (!parent) return\n    \n    const siblings = parent.children || []\n    const allChecked = siblings.every(s => checkedKeys.value.has(s.id))\n    \n    if (allChecked) {\n      checkedKeys.value.add(parent.id)\n    } else {\n      checkedKeys.value.delete(parent.id)\n    }\n    \n    // 递归更新上级\n    updateAncestorsCheckState(parent)\n  }\n  \n  return { checkedKeys, check }\n}\n",[309],{"type":21,"tag":191,"props":310,"children":311},{"__ignoreMap":7},[312],{"type":26,"value":307},{"type":21,"tag":22,"props":314,"children":316},{"id":315},"穿梭框组件",[317],{"type":26,"value":315},{"type":21,"tag":28,"props":319,"children":320},{},[321],{"type":26,"value":322},"穿梭框用于在两个列表之间移动数据。",{"type":21,"tag":183,"props":324,"children":327},{"className":325,"code":326,"language":205,"meta":7},[203],"\u003Ctemplate>\n  \u003Cdiv class=\"transfer\">\n    \u003C!-- 源列表 -->\n    \u003Cdiv class=\"transfer-panel\">\n      \u003Cdiv class=\"transfer-header\">\n        \u003CCheckbox \n          :checked=\"isSourceAllSelected\"\n          :indeterminate=\"isSourcePartialSelected\"\n          @change=\"toggleSourceAll\"\n        />\n        \u003Cspan class=\"header-title\">{{ sourceTitle }}\u003C/span>\n        \u003Cspan class=\"header-count\">{{ sourceChecked.size }}/{{ sourceData.length }}\u003C/span>\n      \u003C/div>\n      \n      \u003Cdiv class=\"transfer-search\" v-if=\"filterable\">\n        \u003CInput v-model=\"sourceQuery\" placeholder=\"搜索...\" />\n      \u003C/div>\n      \n      \u003Cul class=\"transfer-list\">\n        \u003Cli \n          v-for=\"item in filteredSourceData\"\n          :key=\"item.key\"\n          class=\"transfer-item\"\n          :class=\"{ 'is-disabled': item.disabled }\"\n        >\n          \u003CCheckbox\n            :checked=\"sourceChecked.has(item.key)\"\n            :disabled=\"item.disabled\"\n            @change=\"toggleSourceItem(item)\"\n          />\n          \u003Cspan class=\"item-label\">{{ item.label }}\u003C/span>\n        \u003C/li>\n      \u003C/ul>\n    \u003C/div>\n    \n    \u003C!-- 操作按钮 -->\n    \u003Cdiv class=\"transfer-operations\">\n      \u003CButton \n        type=\"primary\" \n        :disabled=\"sourceChecked.size === 0\"\n        @click=\"moveToTarget\"\n      >\n        \u003CIcon name=\"arrow-right\" />\n      \u003C/Button>\n      \u003CButton \n        type=\"primary\" \n        :disabled=\"targetChecked.size === 0\"\n        @click=\"moveToSource\"\n      >\n        \u003CIcon name=\"arrow-left\" />\n      \u003C/Button>\n    \u003C/div>\n    \n    \u003C!-- 目标列表（结构同源列表） -->\n    \u003Cdiv class=\"transfer-panel\">\n      \u003C!-- ... -->\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\ninterface TransferItem {\n  key: string\n  label: string\n  disabled?: boolean\n}\n\nconst props = defineProps\u003C{\n  data: TransferItem[]\n  modelValue: string[]\n  sourceTitle?: string\n  targetTitle?: string\n  filterable?: boolean\n}>()\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\n// 分割源数据和目标数据\nconst sourceData = computed(() => \n  props.data.filter(item => !props.modelValue.includes(item.key))\n)\n\nconst targetData = computed(() => \n  props.data.filter(item => props.modelValue.includes(item.key))\n)\n\n// 选中状态\nconst sourceChecked = ref\u003CSet\u003Cstring>>(new Set())\nconst targetChecked = ref\u003CSet\u003Cstring>>(new Set())\n\n// 移动到目标列表\nfunction moveToTarget() {\n  const newValue = [...props.modelValue, ...sourceChecked.value]\n  emit('update:modelValue', newValue)\n  emit('change', newValue, 'right', [...sourceChecked.value])\n  sourceChecked.value.clear()\n}\n\n// 移动回源列表\nfunction moveToSource() {\n  const newValue = props.modelValue.filter(key => !targetChecked.value.has(key))\n  emit('update:modelValue', newValue)\n  emit('change', newValue, 'left', [...targetChecked.value])\n  targetChecked.value.clear()\n}\n\u003C/script>\n",[328],{"type":21,"tag":191,"props":329,"children":330},{"__ignoreMap":7},[331],{"type":26,"value":326},{"type":21,"tag":22,"props":333,"children":335},{"id":334},"可视化编辑器",[336],{"type":26,"value":334},{"type":21,"tag":28,"props":338,"children":339},{},[340],{"type":26,"value":341},"可视化编辑器是最复杂的交互组件之一，需要处理拖拽、缩放、对齐等多种操作。",{"type":21,"tag":177,"props":343,"children":345},{"id":344},"画布核心结构",[346],{"type":26,"value":344},{"type":21,"tag":183,"props":348,"children":351},{"className":349,"code":350,"language":205,"meta":7},[203],"\u003Ctemplate>\n  \u003Cdiv class=\"visual-editor\">\n    \u003C!-- 工具栏 -->\n    \u003Cdiv class=\"editor-toolbar\">\n      \u003CToolButton \n        v-for=\"tool in tools\" \n        :key=\"tool.id\"\n        :active=\"currentTool === tool.id\"\n        @click=\"selectTool(tool.id)\"\n      >\n        \u003CIcon :name=\"tool.icon\" />\n      \u003C/ToolButton>\n    \u003C/div>\n    \n    \u003C!-- 画布区域 -->\n    \u003Cdiv \n      ref=\"canvasContainer\"\n      class=\"editor-canvas-container\"\n      @wheel=\"handleZoom\"\n      @mousedown=\"handleCanvasMouseDown\"\n    >\n      \u003Cdiv \n        class=\"editor-canvas\"\n        :style=\"{\n          transform: `scale(${zoom}) translate(${pan.x}px, ${pan.y}px)`,\n          width: `${canvasSize.width}px`,\n          height: `${canvasSize.height}px`\n        }\"\n      >\n        \u003C!-- 网格背景 -->\n        \u003CGridBackground v-if=\"showGrid\" :size=\"gridSize\" />\n        \n        \u003C!-- 元素渲染 -->\n        \u003CEditorElement\n          v-for=\"element in elements\"\n          :key=\"element.id\"\n          :element=\"element\"\n          :selected=\"selectedIds.has(element.id)\"\n          @select=\"handleSelect\"\n          @move=\"handleMove\"\n          @resize=\"handleResize\"\n        />\n        \n        \u003C!-- 选择框 -->\n        \u003CSelectionBox \n          v-if=\"isSelecting\" \n          :start=\"selectionStart\"\n          :end=\"selectionEnd\"\n        />\n        \n        \u003C!-- 对齐辅助线 -->\n        \u003CAlignmentGuides :guides=\"activeGuides\" />\n      \u003C/div>\n    \u003C/div>\n    \n    \u003C!-- 属性面板 -->\n    \u003Cdiv class=\"editor-properties\">\n      \u003CPropertyPanel :element=\"selectedElement\" @update=\"updateElement\" />\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n",[352],{"type":21,"tag":191,"props":353,"children":354},{"__ignoreMap":7},[355],{"type":26,"value":350},{"type":21,"tag":177,"props":357,"children":359},{"id":358},"元素变换处理",[360],{"type":26,"value":358},{"type":21,"tag":183,"props":362,"children":365},{"className":363,"code":364,"language":188,"meta":7},[186],"interface Transform {\n  x: number\n  y: number\n  width: number\n  height: number\n  rotation: number\n}\n\nfunction useElementTransform(element: Ref\u003CEditorElement>) {\n  const isMoving = ref(false)\n  const isResizing = ref(false)\n  const startTransform = ref\u003CTransform | null>(null)\n  const startMouse = ref({ x: 0, y: 0 })\n  \n  // 移动处理\n  function startMove(event: MouseEvent) {\n    isMoving.value = true\n    startTransform.value = { ...element.value.transform }\n    startMouse.value = { x: event.clientX, y: event.clientY }\n    \n    document.addEventListener('mousemove', handleMove)\n    document.addEventListener('mouseup', stopMove)\n  }\n  \n  function handleMove(event: MouseEvent) {\n    if (!startTransform.value) return\n    \n    const dx = event.clientX - startMouse.value.x\n    const dy = event.clientY - startMouse.value.y\n    \n    element.value.transform.x = startTransform.value.x + dx\n    element.value.transform.y = startTransform.value.y + dy\n    \n    // 计算对齐吸附\n    if (snapToGrid.value) {\n      element.value.transform.x = Math.round(element.value.transform.x / gridSize) * gridSize\n      element.value.transform.y = Math.round(element.value.transform.y / gridSize) * gridSize\n    }\n  }\n  \n  // 缩放处理\n  function startResize(handle: string, event: MouseEvent) {\n    isResizing.value = true\n    resizeHandle.value = handle\n    startTransform.value = { ...element.value.transform }\n    startMouse.value = { x: event.clientX, y: event.clientY }\n    \n    document.addEventListener('mousemove', handleResize)\n    document.addEventListener('mouseup', stopResize)\n  }\n  \n  function handleResize(event: MouseEvent) {\n    if (!startTransform.value) return\n    \n    const dx = event.clientX - startMouse.value.x\n    const dy = event.clientY - startMouse.value.y\n    \n    // 根据不同的控制点计算新尺寸\n    const newTransform = calculateResizedTransform(\n      startTransform.value,\n      resizeHandle.value,\n      dx, dy,\n      event.shiftKey  // Shift 键保持比例\n    )\n    \n    Object.assign(element.value.transform, newTransform)\n  }\n  \n  return { startMove, startResize, isMoving, isResizing }\n}\n",[366],{"type":21,"tag":191,"props":367,"children":368},{"__ignoreMap":7},[369],{"type":26,"value":364},{"type":21,"tag":22,"props":371,"children":373},{"id":372},"性能优化策略",[374],{"type":26,"value":372},{"type":21,"tag":28,"props":376,"children":377},{},[378],{"type":26,"value":379},"复杂交互组件需要特别注意性能优化：",{"type":21,"tag":44,"props":381,"children":382},{},[383,404],{"type":21,"tag":48,"props":384,"children":385},{},[386],{"type":21,"tag":52,"props":387,"children":388},{},[389,394,399],{"type":21,"tag":56,"props":390,"children":391},{},[392],{"type":26,"value":393},"优化策略",{"type":21,"tag":56,"props":395,"children":396},{},[397],{"type":26,"value":398},"适用场景",{"type":21,"tag":56,"props":400,"children":401},{},[402],{"type":26,"value":403},"实现方式",{"type":21,"tag":72,"props":405,"children":406},{},[407,425,443,461,479],{"type":21,"tag":52,"props":408,"children":409},{},[410,415,420],{"type":21,"tag":79,"props":411,"children":412},{},[413],{"type":26,"value":414},"虚拟滚动",{"type":21,"tag":79,"props":416,"children":417},{},[418],{"type":26,"value":419},"大量数据列表",{"type":21,"tag":79,"props":421,"children":422},{},[423],{"type":26,"value":424},"只渲染可见区域",{"type":21,"tag":52,"props":426,"children":427},{},[428,433,438],{"type":21,"tag":79,"props":429,"children":430},{},[431],{"type":26,"value":432},"防抖节流",{"type":21,"tag":79,"props":434,"children":435},{},[436],{"type":26,"value":437},"频繁触发事件",{"type":21,"tag":79,"props":439,"children":440},{},[441],{"type":26,"value":442},"限制事件处理频率",{"type":21,"tag":52,"props":444,"children":445},{},[446,451,456],{"type":21,"tag":79,"props":447,"children":448},{},[449],{"type":26,"value":450},"分层渲染",{"type":21,"tag":79,"props":452,"children":453},{},[454],{"type":26,"value":455},"画布编辑器",{"type":21,"tag":79,"props":457,"children":458},{},[459],{"type":26,"value":460},"分离静态层和动态层",{"type":21,"tag":52,"props":462,"children":463},{},[464,469,474],{"type":21,"tag":79,"props":465,"children":466},{},[467],{"type":26,"value":468},"离屏渲染",{"type":21,"tag":79,"props":470,"children":471},{},[472],{"type":26,"value":473},"复杂预览",{"type":21,"tag":79,"props":475,"children":476},{},[477],{"type":26,"value":478},"使用 OffscreenCanvas",{"type":21,"tag":52,"props":480,"children":481},{},[482,487,492],{"type":21,"tag":79,"props":483,"children":484},{},[485],{"type":26,"value":486},"懒加载",{"type":21,"tag":79,"props":488,"children":489},{},[490],{"type":26,"value":491},"树形数据",{"type":21,"tag":79,"props":493,"children":494},{},[495],{"type":26,"value":496},"按需加载子节点",{"type":21,"tag":183,"props":498,"children":501},{"className":499,"code":500,"language":188,"meta":7},[186],"// 拖拽过程中使用 requestAnimationFrame 优化\nfunction optimizedDragHandler() {\n  let rafId: number | null = null\n  \n  return (event: MouseEvent) => {\n    if (rafId) return\n    \n    rafId = requestAnimationFrame(() => {\n      // 执行实际的位置更新\n      updateElementPosition(event.clientX, event.clientY)\n      rafId = null\n    })\n  }\n}\n",[502],{"type":21,"tag":191,"props":503,"children":504},{"__ignoreMap":7},[505],{"type":26,"value":500},{"type":21,"tag":22,"props":507,"children":509},{"id":508},"总结",[510],{"type":26,"value":508},{"type":21,"tag":28,"props":512,"children":513},{},[514],{"type":26,"value":515},"复杂交互组件的设计需要关注以下要点：",{"type":21,"tag":517,"props":518,"children":519},"ol",{},[520,532,542,552,562],{"type":21,"tag":521,"props":522,"children":523},"li",{},[524,530],{"type":21,"tag":525,"props":526,"children":527},"strong",{},[528],{"type":26,"value":529},"状态管理清晰",{"type":26,"value":531},"：复杂交互涉及多种状态转换，需要清晰的状态机设计",{"type":21,"tag":521,"props":533,"children":534},{},[535,540],{"type":21,"tag":525,"props":536,"children":537},{},[538],{"type":26,"value":539},"事件处理精准",{"type":26,"value":541},"：正确处理各类鼠标/触摸事件，避免事件冲突",{"type":21,"tag":521,"props":543,"children":544},{},[545,550],{"type":21,"tag":525,"props":546,"children":547},{},[548],{"type":26,"value":549},"视觉反馈即时",{"type":26,"value":551},"：拖拽、选择等操作需要即时的视觉反馈",{"type":21,"tag":521,"props":553,"children":554},{},[555,560],{"type":21,"tag":525,"props":556,"children":557},{},[558],{"type":26,"value":559},"性能优化到位",{"type":26,"value":561},"：大数据量场景需要虚拟化、节流等优化手段",{"type":21,"tag":521,"props":563,"children":564},{},[565,570],{"type":21,"tag":525,"props":566,"children":567},{},[568],{"type":26,"value":569},"可访问性支持",{"type":26,"value":571},"：复杂交互也应支持键盘操作和屏幕阅读器",{"type":21,"tag":28,"props":573,"children":574},{},[575],{"type":26,"value":576},"通过合理的架构设计和细节打磨，可以构建出既强大又易用的复杂交互组件。",{"title":7,"searchDepth":578,"depth":578,"links":579},3,[580,582,583,588,592,596,597,601,602],{"id":8,"depth":581,"text":8},2,{"id":35,"depth":581,"text":35},{"id":168,"depth":581,"text":168,"children":584},[585,586,587],{"id":179,"depth":578,"text":179},{"id":197,"depth":578,"text":197},{"id":213,"depth":578,"text":216},{"id":233,"depth":581,"text":236,"children":589},[590,591],{"id":244,"depth":578,"text":244},{"id":258,"depth":578,"text":258},{"id":272,"depth":581,"text":272,"children":593},[594,595],{"id":282,"depth":578,"text":282},{"id":296,"depth":578,"text":296},{"id":315,"depth":581,"text":315},{"id":334,"depth":581,"text":334,"children":598},[599,600],{"id":344,"depth":578,"text":344},{"id":358,"depth":578,"text":358},{"id":372,"depth":581,"text":372},{"id":508,"depth":581,"text":508},"markdown","content:topics:design:complex-interaction-component-guide.md","content","topics/design/complex-interaction-component-guide.md","topics/design/complex-interaction-component-guide","md",[610,968,1270],{"_path":611,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":612,"description":613,"keywords":614,"image":620,"author":621,"date":622,"readingTime":623,"topic":5,"body":624,"_type":603,"_id":965,"_source":605,"_file":966,"_stem":967,"_extension":608},"/topics/design/button-component-design","按钮组件设计详解","学习按钮样式、交互状态、无障碍性和最佳实践",[615,616,617,618,619],"按钮设计","Button Component","交互状态","UI 组件","用户体验","/images/topics/button-design.jpg","HTMLPAGE 团队","2025-12-08",18,{"type":18,"children":625,"toc":947},[626,630,635,640,646,657,663,672,678,687,691,697,708,714,723,729,738,743,752,757,768,773,782,787,799,834,845,888,893],{"type":21,"tag":22,"props":627,"children":628},{"id":612},[629],{"type":26,"value":612},{"type":21,"tag":28,"props":631,"children":632},{},[633],{"type":26,"value":634},"按钮是 UI 中最重要的交互元素。优秀的按钮设计能够指导用户行为。",{"type":21,"tag":22,"props":636,"children":638},{"id":637},"按钮类型",[639],{"type":26,"value":637},{"type":21,"tag":177,"props":641,"children":643},{"id":642},"primary-button主按钮",[644],{"type":26,"value":645},"Primary Button（主按钮）",{"type":21,"tag":183,"props":647,"children":652},{"className":648,"code":650,"language":651,"meta":7},[649],"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",[653],{"type":21,"tag":191,"props":654,"children":655},{"__ignoreMap":7},[656],{"type":26,"value":650},{"type":21,"tag":177,"props":658,"children":660},{"id":659},"secondary-button次按钮",[661],{"type":26,"value":662},"Secondary Button（次按钮）",{"type":21,"tag":183,"props":664,"children":667},{"className":665,"code":666,"language":651,"meta":7},[649],".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",[668],{"type":21,"tag":191,"props":669,"children":670},{"__ignoreMap":7},[671],{"type":26,"value":666},{"type":21,"tag":177,"props":673,"children":675},{"id":674},"danger-button危险按钮",[676],{"type":26,"value":677},"Danger Button（危险按钮）",{"type":21,"tag":183,"props":679,"children":682},{"className":680,"code":681,"language":651,"meta":7},[649],".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",[683],{"type":21,"tag":191,"props":684,"children":685},{"__ignoreMap":7},[686],{"type":26,"value":681},{"type":21,"tag":22,"props":688,"children":689},{"id":617},[690],{"type":26,"value":617},{"type":21,"tag":177,"props":692,"children":694},{"id":693},"loading-状态",[695],{"type":26,"value":696},"Loading 状态",{"type":21,"tag":183,"props":698,"children":703},{"className":699,"code":701,"language":702,"meta":7},[700],"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",[704],{"type":21,"tag":191,"props":705,"children":706},{"__ignoreMap":7},[707],{"type":26,"value":701},{"type":21,"tag":177,"props":709,"children":711},{"id":710},"disabled-状态",[712],{"type":26,"value":713},"Disabled 状态",{"type":21,"tag":183,"props":715,"children":718},{"className":716,"code":717,"language":651,"meta":7},[649],".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",[719],{"type":21,"tag":191,"props":720,"children":721},{"__ignoreMap":7},[722],{"type":26,"value":717},{"type":21,"tag":177,"props":724,"children":726},{"id":725},"focus-状态",[727],{"type":26,"value":728},"Focus 状态",{"type":21,"tag":183,"props":730,"children":733},{"className":731,"code":732,"language":651,"meta":7},[649],".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",[734],{"type":21,"tag":191,"props":735,"children":736},{"__ignoreMap":7},[737],{"type":26,"value":732},{"type":21,"tag":22,"props":739,"children":741},{"id":740},"按钮大小",[742],{"type":26,"value":740},{"type":21,"tag":183,"props":744,"children":747},{"className":745,"code":746,"language":651,"meta":7},[649],"/* 小按钮 */\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",[748],{"type":21,"tag":191,"props":749,"children":750},{"__ignoreMap":7},[751],{"type":26,"value":746},{"type":21,"tag":22,"props":753,"children":755},{"id":754},"无障碍性",[756],{"type":26,"value":754},{"type":21,"tag":183,"props":758,"children":763},{"className":759,"code":761,"language":762,"meta":7},[760],"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",[764],{"type":21,"tag":191,"props":765,"children":766},{"__ignoreMap":7},[767],{"type":26,"value":761},{"type":21,"tag":22,"props":769,"children":771},{"id":770},"完整组件示例",[772],{"type":26,"value":770},{"type":21,"tag":183,"props":774,"children":777},{"className":775,"code":776,"language":702,"meta":7},[700],"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",[778],{"type":21,"tag":191,"props":779,"children":780},{"__ignoreMap":7},[781],{"type":26,"value":776},{"type":21,"tag":22,"props":783,"children":785},{"id":784},"最佳实践",[786],{"type":26,"value":784},{"type":21,"tag":28,"props":788,"children":789},{},[790,792,797],{"type":26,"value":791},"✅ ",{"type":21,"tag":525,"props":793,"children":794},{},[795],{"type":26,"value":796},"应该做的事",{"type":26,"value":798},":",{"type":21,"tag":800,"props":801,"children":802},"ul",{},[803,808,813,824,829],{"type":21,"tag":521,"props":804,"children":805},{},[806],{"type":26,"value":807},"最小触摸目标 44x44px",{"type":21,"tag":521,"props":809,"children":810},{},[811],{"type":26,"value":812},"清晰的视觉反馈",{"type":21,"tag":521,"props":814,"children":815},{},[816,818],{"type":26,"value":817},"使用语义 HTML ",{"type":21,"tag":191,"props":819,"children":821},{"className":820},[],[822],{"type":26,"value":823},"\u003Cbutton>",{"type":21,"tag":521,"props":825,"children":826},{},[827],{"type":26,"value":828},"提供加载状态反馈",{"type":21,"tag":521,"props":830,"children":831},{},[832],{"type":26,"value":833},"支持键盘导航",{"type":21,"tag":28,"props":835,"children":836},{},[837,839,844],{"type":26,"value":838},"❌ ",{"type":21,"tag":525,"props":840,"children":841},{},[842],{"type":26,"value":843},"不应该做的事",{"type":26,"value":798},{"type":21,"tag":800,"props":846,"children":847},{},[848,861,866,871,876],{"type":21,"tag":521,"props":849,"children":850},{},[851,853,859],{"type":26,"value":852},"使用 ",{"type":21,"tag":191,"props":854,"children":856},{"className":855},[],[857],{"type":26,"value":858},"\u003Cdiv>",{"type":26,"value":860}," 模拟按钮",{"type":21,"tag":521,"props":862,"children":863},{},[864],{"type":26,"value":865},"隐藏焦点指示器",{"type":21,"tag":521,"props":867,"children":868},{},[869],{"type":26,"value":870},"过多的按钮样式",{"type":21,"tag":521,"props":872,"children":873},{},[874],{"type":26,"value":875},"忽视禁用状态",{"type":21,"tag":521,"props":877,"children":878},{},[879,880,886],{"type":26,"value":852},{"type":21,"tag":191,"props":881,"children":883},{"className":882},[],[884],{"type":26,"value":885},"\u003Ca>",{"type":26,"value":887}," 代替按钮",{"type":21,"tag":22,"props":889,"children":891},{"id":890},"测试清单",[892],{"type":26,"value":890},{"type":21,"tag":800,"props":894,"children":897},{"className":895},[896],"contains-task-list",[898,911,920,929,938],{"type":21,"tag":521,"props":899,"children":902},{"className":900},[901],"task-list-item",[903,909],{"type":21,"tag":904,"props":905,"children":908},"input",{"disabled":906,"type":907},true,"checkbox",[],{"type":26,"value":910}," 在各种浏览器中测试",{"type":21,"tag":521,"props":912,"children":914},{"className":913},[901],[915,918],{"type":21,"tag":904,"props":916,"children":917},{"disabled":906,"type":907},[],{"type":26,"value":919}," 验证键盘导航",{"type":21,"tag":521,"props":921,"children":923},{"className":922},[901],[924,927],{"type":21,"tag":904,"props":925,"children":926},{"disabled":906,"type":907},[],{"type":26,"value":928}," 检查色彩对比度",{"type":21,"tag":521,"props":930,"children":932},{"className":931},[901],[933,936],{"type":21,"tag":904,"props":934,"children":935},{"disabled":906,"type":907},[],{"type":26,"value":937}," 测试触摸设备",{"type":21,"tag":521,"props":939,"children":941},{"className":940},[901],[942,945],{"type":21,"tag":904,"props":943,"children":944},{"disabled":906,"type":907},[],{"type":26,"value":946}," 屏幕阅读器兼容性",{"title":7,"searchDepth":578,"depth":578,"links":948},[949,950,955,960,961,962,963,964],{"id":612,"depth":581,"text":612},{"id":637,"depth":581,"text":637,"children":951},[952,953,954],{"id":642,"depth":578,"text":645},{"id":659,"depth":578,"text":662},{"id":674,"depth":578,"text":677},{"id":617,"depth":581,"text":617,"children":956},[957,958,959],{"id":693,"depth":578,"text":696},{"id":710,"depth":578,"text":713},{"id":725,"depth":578,"text":728},{"id":740,"depth":581,"text":740},{"id":754,"depth":581,"text":754},{"id":770,"depth":581,"text":770},{"id":784,"depth":581,"text":784},{"id":890,"depth":581,"text":890},"content:topics:design:button-component-design.md","topics/design/button-component-design.md","topics/design/button-component-design",{"_path":969,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":970,"description":971,"keywords":972,"image":977,"author":621,"date":622,"readingTime":978,"topic":5,"body":979,"_type":603,"_id":1267,"_source":605,"_file":1268,"_stem":1269,"_extension":608},"/topics/design/dark-mode-design","暗黑模式设计完整方案","学习暗黑模式实现、色彩方案、对比度管理和最佳实践",[973,974,975,976,619],"暗黑模式","Dark Mode","色彩系统","CSS 变量","/images/topics/dark-mode-design.jpg",20,{"type":18,"children":980,"toc":1250},[981,985,990,995,1001,1010,1016,1025,1030,1036,1045,1051,1062,1068,1077,1082,1091,1096,1105,1110,1119,1123,1132,1160,1169,1197,1201],{"type":21,"tag":22,"props":982,"children":983},{"id":970},[984],{"type":26,"value":970},{"type":21,"tag":28,"props":986,"children":987},{},[988],{"type":26,"value":989},"暗黑模式已成为现代应用的标准功能。它能够减少眼睛疲劳、节省电池、改善用户体验。",{"type":21,"tag":22,"props":991,"children":993},{"id":992},"核心色彩系统",[994],{"type":26,"value":992},{"type":21,"tag":177,"props":996,"children":998},{"id":997},"light-mode-配色",[999],{"type":26,"value":1000},"Light Mode 配色",{"type":21,"tag":183,"props":1002,"children":1005},{"className":1003,"code":1004,"language":651,"meta":7},[649],":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",[1006],{"type":21,"tag":191,"props":1007,"children":1008},{"__ignoreMap":7},[1009],{"type":26,"value":1004},{"type":21,"tag":177,"props":1011,"children":1013},{"id":1012},"dark-mode-配色",[1014],{"type":26,"value":1015},"Dark Mode 配色",{"type":21,"tag":183,"props":1017,"children":1020},{"className":1018,"code":1019,"language":651,"meta":7},[649],"@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",[1021],{"type":21,"tag":191,"props":1022,"children":1023},{"__ignoreMap":7},[1024],{"type":26,"value":1019},{"type":21,"tag":22,"props":1026,"children":1028},{"id":1027},"实现方案",[1029],{"type":26,"value":1027},{"type":21,"tag":177,"props":1031,"children":1033},{"id":1032},"方案-1prefers-color-scheme",[1034],{"type":26,"value":1035},"方案 1：prefers-color-scheme",{"type":21,"tag":183,"props":1037,"children":1040},{"className":1038,"code":1039,"language":651,"meta":7},[649],"/* 自动跟随系统设置 */\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",[1041],{"type":21,"tag":191,"props":1042,"children":1043},{"__ignoreMap":7},[1044],{"type":26,"value":1039},{"type":21,"tag":177,"props":1046,"children":1048},{"id":1047},"方案-2javascript-切换",[1049],{"type":26,"value":1050},"方案 2：JavaScript 切换",{"type":21,"tag":183,"props":1052,"children":1057},{"className":1053,"code":1055,"language":1056,"meta":7},[1054],"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",[1058],{"type":21,"tag":191,"props":1059,"children":1060},{"__ignoreMap":7},[1061],{"type":26,"value":1055},{"type":21,"tag":177,"props":1063,"children":1065},{"id":1064},"方案-3css-variables-javascript",[1066],{"type":26,"value":1067},"方案 3：CSS Variables + JavaScript",{"type":21,"tag":183,"props":1069,"children":1072},{"className":1070,"code":1071,"language":1056,"meta":7},[1054],"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",[1073],{"type":21,"tag":191,"props":1074,"children":1075},{"__ignoreMap":7},[1076],{"type":26,"value":1071},{"type":21,"tag":22,"props":1078,"children":1080},{"id":1079},"对比度管理",[1081],{"type":26,"value":1079},{"type":21,"tag":183,"props":1083,"children":1086},{"className":1084,"code":1085,"language":651,"meta":7},[649],"/* 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",[1087],{"type":21,"tag":191,"props":1088,"children":1089},{"__ignoreMap":7},[1090],{"type":26,"value":1085},{"type":21,"tag":22,"props":1092,"children":1094},{"id":1093},"图片和图表处理",[1095],{"type":26,"value":1093},{"type":21,"tag":183,"props":1097,"children":1100},{"className":1098,"code":1099,"language":762,"meta":7},[760],"\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",[1101],{"type":21,"tag":191,"props":1102,"children":1103},{"__ignoreMap":7},[1104],{"type":26,"value":1099},{"type":21,"tag":22,"props":1106,"children":1108},{"id":1107},"完整示例",[1109],{"type":26,"value":1107},{"type":21,"tag":183,"props":1111,"children":1114},{"className":1112,"code":1113,"language":702,"meta":7},[700],"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",[1115],{"type":21,"tag":191,"props":1116,"children":1117},{"__ignoreMap":7},[1118],{"type":26,"value":1113},{"type":21,"tag":22,"props":1120,"children":1121},{"id":784},[1122],{"type":26,"value":784},{"type":21,"tag":28,"props":1124,"children":1125},{},[1126,1127,1131],{"type":26,"value":791},{"type":21,"tag":525,"props":1128,"children":1129},{},[1130],{"type":26,"value":796},{"type":26,"value":798},{"type":21,"tag":800,"props":1133,"children":1134},{},[1135,1140,1145,1150,1155],{"type":21,"tag":521,"props":1136,"children":1137},{},[1138],{"type":26,"value":1139},"支持系统偏好",{"type":21,"tag":521,"props":1141,"children":1142},{},[1143],{"type":26,"value":1144},"提供手动切换选项",{"type":21,"tag":521,"props":1146,"children":1147},{},[1148],{"type":26,"value":1149},"确保足够的对比度",{"type":21,"tag":521,"props":1151,"children":1152},{},[1153],{"type":26,"value":1154},"优化图片和图表",{"type":21,"tag":521,"props":1156,"children":1157},{},[1158],{"type":26,"value":1159},"防止加载闪烁",{"type":21,"tag":28,"props":1161,"children":1162},{},[1163,1164,1168],{"type":26,"value":838},{"type":21,"tag":525,"props":1165,"children":1166},{},[1167],{"type":26,"value":843},{"type":26,"value":798},{"type":21,"tag":800,"props":1170,"children":1171},{},[1172,1177,1182,1187,1192],{"type":21,"tag":521,"props":1173,"children":1174},{},[1175],{"type":26,"value":1176},"强制单一模式",{"type":21,"tag":521,"props":1178,"children":1179},{},[1180],{"type":26,"value":1181},"忽视性能影响",{"type":21,"tag":521,"props":1183,"children":1184},{},[1185],{"type":26,"value":1186},"使用相同的颜色",{"type":21,"tag":521,"props":1188,"children":1189},{},[1190],{"type":26,"value":1191},"忘记保存用户偏好",{"type":21,"tag":521,"props":1193,"children":1194},{},[1195],{"type":26,"value":1196},"过度使用深色背景",{"type":21,"tag":22,"props":1198,"children":1199},{"id":890},[1200],{"type":26,"value":890},{"type":21,"tag":800,"props":1202,"children":1204},{"className":1203},[896],[1205,1214,1223,1232,1241],{"type":21,"tag":521,"props":1206,"children":1208},{"className":1207},[901],[1209,1212],{"type":21,"tag":904,"props":1210,"children":1211},{"disabled":906,"type":907},[],{"type":26,"value":1213}," 在浅色和深色模式下测试所有页面",{"type":21,"tag":521,"props":1215,"children":1217},{"className":1216},[901],[1218,1221],{"type":21,"tag":904,"props":1219,"children":1220},{"disabled":906,"type":907},[],{"type":26,"value":1222}," 检查颜色对比度符合 WCAG 标准",{"type":21,"tag":521,"props":1224,"children":1226},{"className":1225},[901],[1227,1230],{"type":21,"tag":904,"props":1228,"children":1229},{"disabled":906,"type":907},[],{"type":26,"value":1231}," 验证图片和图表在两种模式下清晰",{"type":21,"tag":521,"props":1233,"children":1235},{"className":1234},[901],[1236,1239],{"type":21,"tag":904,"props":1237,"children":1238},{"disabled":906,"type":907},[],{"type":26,"value":1240}," 测试主题切换的平滑性",{"type":21,"tag":521,"props":1242,"children":1244},{"className":1243},[901],[1245,1248],{"type":21,"tag":904,"props":1246,"children":1247},{"disabled":906,"type":907},[],{"type":26,"value":1249}," 检查用户偏好是否被保存",{"title":7,"searchDepth":578,"depth":578,"links":1251},[1252,1253,1257,1262,1263,1264,1265,1266],{"id":970,"depth":581,"text":970},{"id":992,"depth":581,"text":992,"children":1254},[1255,1256],{"id":997,"depth":578,"text":1000},{"id":1012,"depth":578,"text":1015},{"id":1027,"depth":581,"text":1027,"children":1258},[1259,1260,1261],{"id":1032,"depth":578,"text":1035},{"id":1047,"depth":578,"text":1050},{"id":1064,"depth":578,"text":1067},{"id":1079,"depth":581,"text":1079},{"id":1093,"depth":581,"text":1093},{"id":1107,"depth":581,"text":1107},{"id":784,"depth":581,"text":784},{"id":890,"depth":581,"text":890},"content:topics:design:dark-mode-design.md","topics/design/dark-mode-design.md","topics/design/dark-mode-design",{"_path":1271,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1272,"description":1273,"keywords":1274,"image":1279,"author":1280,"date":622,"readingTime":978,"topic":5,"body":1281,"_type":603,"_id":1546,"_source":605,"_file":1547,"_stem":1548,"_extension":608},"/topics/design/form-controls-design","表单控件设计规范","学习输入框、选择框、复选框等表单控件的设计和实现",[1275,1276,1277,1278,619],"表单设计","Form Controls","输入框","验证反馈","/images/topics/form-controls-design.jpg","AI Content Team",{"type":18,"children":1282,"toc":1532},[1283,1287,1292,1297,1302,1311,1316,1325,1329,1338,1343,1352,1357,1366,1371,1380,1385,1394,1398,1407,1433,1442,1470,1474],{"type":21,"tag":22,"props":1284,"children":1285},{"id":1272},[1286],{"type":26,"value":1272},{"type":21,"tag":28,"props":1288,"children":1289},{},[1290],{"type":26,"value":1291},"优秀的表单设计能够提高用户完成率和满意度。",{"type":21,"tag":22,"props":1293,"children":1295},{"id":1294},"输入框设计",[1296],{"type":26,"value":1294},{"type":21,"tag":177,"props":1298,"children":1300},{"id":1299},"基础文本输入",[1301],{"type":26,"value":1299},{"type":21,"tag":183,"props":1303,"children":1306},{"className":1304,"code":1305,"language":651,"meta":7},[649],".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",[1307],{"type":21,"tag":191,"props":1308,"children":1309},{"__ignoreMap":7},[1310],{"type":26,"value":1305},{"type":21,"tag":177,"props":1312,"children":1314},{"id":1313},"标签和提示",[1315],{"type":26,"value":1313},{"type":21,"tag":183,"props":1317,"children":1320},{"className":1318,"code":1319,"language":762,"meta":7},[760],"\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",[1321],{"type":21,"tag":191,"props":1322,"children":1323},{"__ignoreMap":7},[1324],{"type":26,"value":1319},{"type":21,"tag":22,"props":1326,"children":1327},{"id":1278},[1328],{"type":26,"value":1278},{"type":21,"tag":183,"props":1330,"children":1333},{"className":1331,"code":1332,"language":702,"meta":7},[700],"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",[1334],{"type":21,"tag":191,"props":1335,"children":1336},{"__ignoreMap":7},[1337],{"type":26,"value":1332},{"type":21,"tag":22,"props":1339,"children":1341},{"id":1340},"选择框设计",[1342],{"type":26,"value":1340},{"type":21,"tag":183,"props":1344,"children":1347},{"className":1345,"code":1346,"language":651,"meta":7},[649],".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",[1348],{"type":21,"tag":191,"props":1349,"children":1350},{"__ignoreMap":7},[1351],{"type":26,"value":1346},{"type":21,"tag":22,"props":1353,"children":1355},{"id":1354},"复选框和单选按钮",[1356],{"type":26,"value":1354},{"type":21,"tag":183,"props":1358,"children":1361},{"className":1359,"code":1360,"language":651,"meta":7},[649],".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",[1362],{"type":21,"tag":191,"props":1363,"children":1364},{"__ignoreMap":7},[1365],{"type":26,"value":1360},{"type":21,"tag":22,"props":1367,"children":1369},{"id":1368},"文本区域",[1370],{"type":26,"value":1368},{"type":21,"tag":183,"props":1372,"children":1375},{"className":1373,"code":1374,"language":651,"meta":7},[649],".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",[1376],{"type":21,"tag":191,"props":1377,"children":1378},{"__ignoreMap":7},[1379],{"type":26,"value":1374},{"type":21,"tag":22,"props":1381,"children":1383},{"id":1382},"完整表单示例",[1384],{"type":26,"value":1382},{"type":21,"tag":183,"props":1386,"children":1389},{"className":1387,"code":1388,"language":702,"meta":7},[700],"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",[1390],{"type":21,"tag":191,"props":1391,"children":1392},{"__ignoreMap":7},[1393],{"type":26,"value":1388},{"type":21,"tag":22,"props":1395,"children":1396},{"id":784},[1397],{"type":26,"value":784},{"type":21,"tag":28,"props":1399,"children":1400},{},[1401,1402,1406],{"type":26,"value":791},{"type":21,"tag":525,"props":1403,"children":1404},{},[1405],{"type":26,"value":796},{"type":26,"value":798},{"type":21,"tag":800,"props":1408,"children":1409},{},[1410,1415,1420,1425,1429],{"type":21,"tag":521,"props":1411,"children":1412},{},[1413],{"type":26,"value":1414},"使用正确的输入类型",{"type":21,"tag":521,"props":1416,"children":1417},{},[1418],{"type":26,"value":1419},"提供实时验证反馈",{"type":21,"tag":521,"props":1421,"children":1422},{},[1423],{"type":26,"value":1424},"清晰的标签和提示",{"type":21,"tag":521,"props":1426,"children":1427},{},[1428],{"type":26,"value":807},{"type":21,"tag":521,"props":1430,"children":1431},{},[1432],{"type":26,"value":833},{"type":21,"tag":28,"props":1434,"children":1435},{},[1436,1437,1441],{"type":26,"value":838},{"type":21,"tag":525,"props":1438,"children":1439},{},[1440],{"type":26,"value":843},{"type":26,"value":798},{"type":21,"tag":800,"props":1443,"children":1444},{},[1445,1450,1455,1460,1465],{"type":21,"tag":521,"props":1446,"children":1447},{},[1448],{"type":26,"value":1449},"隐藏标签",{"type":21,"tag":521,"props":1451,"children":1452},{},[1453],{"type":26,"value":1454},"过度使用占位符",{"type":21,"tag":521,"props":1456,"children":1457},{},[1458],{"type":26,"value":1459},"验证后立即提交",{"type":21,"tag":521,"props":1461,"children":1462},{},[1463],{"type":26,"value":1464},"忽视无障碍性",{"type":21,"tag":521,"props":1466,"children":1467},{},[1468],{"type":26,"value":1469},"复杂的验证规则",{"type":21,"tag":22,"props":1471,"children":1472},{"id":890},[1473],{"type":26,"value":890},{"type":21,"tag":800,"props":1475,"children":1477},{"className":1476},[896],[1478,1487,1496,1505,1514,1523],{"type":21,"tag":521,"props":1479,"children":1481},{"className":1480},[901],[1482,1485],{"type":21,"tag":904,"props":1483,"children":1484},{"disabled":906,"type":907},[],{"type":26,"value":1486}," 所有控件都可用键盘导航",{"type":21,"tag":521,"props":1488,"children":1490},{"className":1489},[901],[1491,1494],{"type":21,"tag":904,"props":1492,"children":1493},{"disabled":906,"type":907},[],{"type":26,"value":1495}," 标签与输入框关联",{"type":21,"tag":521,"props":1497,"children":1499},{"className":1498},[901],[1500,1503],{"type":21,"tag":904,"props":1501,"children":1502},{"disabled":906,"type":907},[],{"type":26,"value":1504}," 验证消息清晰",{"type":21,"tag":521,"props":1506,"children":1508},{"className":1507},[901],[1509,1512],{"type":21,"tag":904,"props":1510,"children":1511},{"disabled":906,"type":907},[],{"type":26,"value":1513}," 色彩对比度足够",{"type":21,"tag":521,"props":1515,"children":1517},{"className":1516},[901],[1518,1521],{"type":21,"tag":904,"props":1519,"children":1520},{"disabled":906,"type":907},[],{"type":26,"value":1522}," 屏幕阅读器兼容",{"type":21,"tag":521,"props":1524,"children":1526},{"className":1525},[901],[1527,1530],{"type":21,"tag":904,"props":1528,"children":1529},{"disabled":906,"type":907},[],{"type":26,"value":1531}," 移动设备测试",{"title":7,"searchDepth":578,"depth":578,"links":1533},[1534,1535,1539,1540,1541,1542,1543,1544,1545],{"id":1272,"depth":581,"text":1272},{"id":1294,"depth":581,"text":1294,"children":1536},[1537,1538],{"id":1299,"depth":578,"text":1299},{"id":1313,"depth":578,"text":1313},{"id":1278,"depth":581,"text":1278},{"id":1340,"depth":581,"text":1340},{"id":1354,"depth":581,"text":1354},{"id":1368,"depth":581,"text":1368},{"id":1382,"depth":581,"text":1382},{"id":784,"depth":581,"text":784},{"id":890,"depth":581,"text":890},"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,"image":11,"head":1550,"body":1553,"_type":603,"_id":604,"_source":605,"_file":606,"_stem":607,"_extension":608},{"meta":1551},[1552],{"name":15,"content":16},{"type":18,"children":1554,"toc":2004},[1555,1559,1563,1567,1571,1670,1674,1678,1682,1690,1694,1702,1706,1710,1718,1722,1726,1730,1738,1742,1750,1754,1758,1762,1770,1774,1778,1786,1790,1794,1802,1806,1810,1814,1822,1826,1834,1838,1842,1941,1949,1953,1957,2000],{"type":21,"tag":22,"props":1556,"children":1557},{"id":8},[1558],{"type":26,"value":8},{"type":21,"tag":28,"props":1560,"children":1561},{},[1562],{"type":26,"value":32},{"type":21,"tag":22,"props":1564,"children":1565},{"id":35},[1566],{"type":26,"value":35},{"type":21,"tag":28,"props":1568,"children":1569},{},[1570],{"type":26,"value":42},{"type":21,"tag":44,"props":1572,"children":1573},{},[1574,1592],{"type":21,"tag":48,"props":1575,"children":1576},{},[1577],{"type":21,"tag":52,"props":1578,"children":1579},{},[1580,1584,1588],{"type":21,"tag":56,"props":1581,"children":1582},{},[1583],{"type":26,"value":60},{"type":21,"tag":56,"props":1585,"children":1586},{},[1587],{"type":26,"value":65},{"type":21,"tag":56,"props":1589,"children":1590},{},[1591],{"type":26,"value":70},{"type":21,"tag":72,"props":1593,"children":1594},{},[1595,1610,1625,1640,1655],{"type":21,"tag":52,"props":1596,"children":1597},{},[1598,1602,1606],{"type":21,"tag":79,"props":1599,"children":1600},{},[1601],{"type":26,"value":83},{"type":21,"tag":79,"props":1603,"children":1604},{},[1605],{"type":26,"value":88},{"type":21,"tag":79,"props":1607,"children":1608},{},[1609],{"type":26,"value":93},{"type":21,"tag":52,"props":1611,"children":1612},{},[1613,1617,1621],{"type":21,"tag":79,"props":1614,"children":1615},{},[1616],{"type":26,"value":101},{"type":21,"tag":79,"props":1618,"children":1619},{},[1620],{"type":26,"value":106},{"type":21,"tag":79,"props":1622,"children":1623},{},[1624],{"type":26,"value":111},{"type":21,"tag":52,"props":1626,"children":1627},{},[1628,1632,1636],{"type":21,"tag":79,"props":1629,"children":1630},{},[1631],{"type":26,"value":119},{"type":21,"tag":79,"props":1633,"children":1634},{},[1635],{"type":26,"value":124},{"type":21,"tag":79,"props":1637,"children":1638},{},[1639],{"type":26,"value":129},{"type":21,"tag":52,"props":1641,"children":1642},{},[1643,1647,1651],{"type":21,"tag":79,"props":1644,"children":1645},{},[1646],{"type":26,"value":137},{"type":21,"tag":79,"props":1648,"children":1649},{},[1650],{"type":26,"value":142},{"type":21,"tag":79,"props":1652,"children":1653},{},[1654],{"type":26,"value":147},{"type":21,"tag":52,"props":1656,"children":1657},{},[1658,1662,1666],{"type":21,"tag":79,"props":1659,"children":1660},{},[1661],{"type":26,"value":155},{"type":21,"tag":79,"props":1663,"children":1664},{},[1665],{"type":26,"value":160},{"type":21,"tag":79,"props":1667,"children":1668},{},[1669],{"type":26,"value":165},{"type":21,"tag":22,"props":1671,"children":1672},{"id":168},[1673],{"type":26,"value":168},{"type":21,"tag":28,"props":1675,"children":1676},{},[1677],{"type":26,"value":175},{"type":21,"tag":177,"props":1679,"children":1680},{"id":179},[1681],{"type":26,"value":179},{"type":21,"tag":183,"props":1683,"children":1685},{"className":1684,"code":187,"language":188,"meta":7},[186],[1686],{"type":21,"tag":191,"props":1687,"children":1688},{"__ignoreMap":7},[1689],{"type":26,"value":187},{"type":21,"tag":177,"props":1691,"children":1692},{"id":197},[1693],{"type":26,"value":197},{"type":21,"tag":183,"props":1695,"children":1697},{"className":1696,"code":204,"language":205,"meta":7},[203],[1698],{"type":21,"tag":191,"props":1699,"children":1700},{"__ignoreMap":7},[1701],{"type":26,"value":204},{"type":21,"tag":177,"props":1703,"children":1704},{"id":213},[1705],{"type":26,"value":216},{"type":21,"tag":28,"props":1707,"children":1708},{},[1709],{"type":26,"value":221},{"type":21,"tag":183,"props":1711,"children":1713},{"className":1712,"code":225,"language":188,"meta":7},[186],[1714],{"type":21,"tag":191,"props":1715,"children":1716},{"__ignoreMap":7},[1717],{"type":26,"value":225},{"type":21,"tag":22,"props":1719,"children":1720},{"id":233},[1721],{"type":26,"value":236},{"type":21,"tag":28,"props":1723,"children":1724},{},[1725],{"type":26,"value":241},{"type":21,"tag":177,"props":1727,"children":1728},{"id":244},[1729],{"type":26,"value":244},{"type":21,"tag":183,"props":1731,"children":1733},{"className":1732,"code":250,"language":188,"meta":7},[186],[1734],{"type":21,"tag":191,"props":1735,"children":1736},{"__ignoreMap":7},[1737],{"type":26,"value":250},{"type":21,"tag":177,"props":1739,"children":1740},{"id":258},[1741],{"type":26,"value":258},{"type":21,"tag":183,"props":1743,"children":1745},{"className":1744,"code":264,"language":205,"meta":7},[203],[1746],{"type":21,"tag":191,"props":1747,"children":1748},{"__ignoreMap":7},[1749],{"type":26,"value":264},{"type":21,"tag":22,"props":1751,"children":1752},{"id":272},[1753],{"type":26,"value":272},{"type":21,"tag":28,"props":1755,"children":1756},{},[1757],{"type":26,"value":279},{"type":21,"tag":177,"props":1759,"children":1760},{"id":282},[1761],{"type":26,"value":282},{"type":21,"tag":183,"props":1763,"children":1765},{"className":1764,"code":288,"language":205,"meta":7},[203],[1766],{"type":21,"tag":191,"props":1767,"children":1768},{"__ignoreMap":7},[1769],{"type":26,"value":288},{"type":21,"tag":177,"props":1771,"children":1772},{"id":296},[1773],{"type":26,"value":296},{"type":21,"tag":28,"props":1775,"children":1776},{},[1777],{"type":26,"value":303},{"type":21,"tag":183,"props":1779,"children":1781},{"className":1780,"code":307,"language":188,"meta":7},[186],[1782],{"type":21,"tag":191,"props":1783,"children":1784},{"__ignoreMap":7},[1785],{"type":26,"value":307},{"type":21,"tag":22,"props":1787,"children":1788},{"id":315},[1789],{"type":26,"value":315},{"type":21,"tag":28,"props":1791,"children":1792},{},[1793],{"type":26,"value":322},{"type":21,"tag":183,"props":1795,"children":1797},{"className":1796,"code":326,"language":205,"meta":7},[203],[1798],{"type":21,"tag":191,"props":1799,"children":1800},{"__ignoreMap":7},[1801],{"type":26,"value":326},{"type":21,"tag":22,"props":1803,"children":1804},{"id":334},[1805],{"type":26,"value":334},{"type":21,"tag":28,"props":1807,"children":1808},{},[1809],{"type":26,"value":341},{"type":21,"tag":177,"props":1811,"children":1812},{"id":344},[1813],{"type":26,"value":344},{"type":21,"tag":183,"props":1815,"children":1817},{"className":1816,"code":350,"language":205,"meta":7},[203],[1818],{"type":21,"tag":191,"props":1819,"children":1820},{"__ignoreMap":7},[1821],{"type":26,"value":350},{"type":21,"tag":177,"props":1823,"children":1824},{"id":358},[1825],{"type":26,"value":358},{"type":21,"tag":183,"props":1827,"children":1829},{"className":1828,"code":364,"language":188,"meta":7},[186],[1830],{"type":21,"tag":191,"props":1831,"children":1832},{"__ignoreMap":7},[1833],{"type":26,"value":364},{"type":21,"tag":22,"props":1835,"children":1836},{"id":372},[1837],{"type":26,"value":372},{"type":21,"tag":28,"props":1839,"children":1840},{},[1841],{"type":26,"value":379},{"type":21,"tag":44,"props":1843,"children":1844},{},[1845,1863],{"type":21,"tag":48,"props":1846,"children":1847},{},[1848],{"type":21,"tag":52,"props":1849,"children":1850},{},[1851,1855,1859],{"type":21,"tag":56,"props":1852,"children":1853},{},[1854],{"type":26,"value":393},{"type":21,"tag":56,"props":1856,"children":1857},{},[1858],{"type":26,"value":398},{"type":21,"tag":56,"props":1860,"children":1861},{},[1862],{"type":26,"value":403},{"type":21,"tag":72,"props":1864,"children":1865},{},[1866,1881,1896,1911,1926],{"type":21,"tag":52,"props":1867,"children":1868},{},[1869,1873,1877],{"type":21,"tag":79,"props":1870,"children":1871},{},[1872],{"type":26,"value":414},{"type":21,"tag":79,"props":1874,"children":1875},{},[1876],{"type":26,"value":419},{"type":21,"tag":79,"props":1878,"children":1879},{},[1880],{"type":26,"value":424},{"type":21,"tag":52,"props":1882,"children":1883},{},[1884,1888,1892],{"type":21,"tag":79,"props":1885,"children":1886},{},[1887],{"type":26,"value":432},{"type":21,"tag":79,"props":1889,"children":1890},{},[1891],{"type":26,"value":437},{"type":21,"tag":79,"props":1893,"children":1894},{},[1895],{"type":26,"value":442},{"type":21,"tag":52,"props":1897,"children":1898},{},[1899,1903,1907],{"type":21,"tag":79,"props":1900,"children":1901},{},[1902],{"type":26,"value":450},{"type":21,"tag":79,"props":1904,"children":1905},{},[1906],{"type":26,"value":455},{"type":21,"tag":79,"props":1908,"children":1909},{},[1910],{"type":26,"value":460},{"type":21,"tag":52,"props":1912,"children":1913},{},[1914,1918,1922],{"type":21,"tag":79,"props":1915,"children":1916},{},[1917],{"type":26,"value":468},{"type":21,"tag":79,"props":1919,"children":1920},{},[1921],{"type":26,"value":473},{"type":21,"tag":79,"props":1923,"children":1924},{},[1925],{"type":26,"value":478},{"type":21,"tag":52,"props":1927,"children":1928},{},[1929,1933,1937],{"type":21,"tag":79,"props":1930,"children":1931},{},[1932],{"type":26,"value":486},{"type":21,"tag":79,"props":1934,"children":1935},{},[1936],{"type":26,"value":491},{"type":21,"tag":79,"props":1938,"children":1939},{},[1940],{"type":26,"value":496},{"type":21,"tag":183,"props":1942,"children":1944},{"className":1943,"code":500,"language":188,"meta":7},[186],[1945],{"type":21,"tag":191,"props":1946,"children":1947},{"__ignoreMap":7},[1948],{"type":26,"value":500},{"type":21,"tag":22,"props":1950,"children":1951},{"id":508},[1952],{"type":26,"value":508},{"type":21,"tag":28,"props":1954,"children":1955},{},[1956],{"type":26,"value":515},{"type":21,"tag":517,"props":1958,"children":1959},{},[1960,1968,1976,1984,1992],{"type":21,"tag":521,"props":1961,"children":1962},{},[1963,1967],{"type":21,"tag":525,"props":1964,"children":1965},{},[1966],{"type":26,"value":529},{"type":26,"value":531},{"type":21,"tag":521,"props":1969,"children":1970},{},[1971,1975],{"type":21,"tag":525,"props":1972,"children":1973},{},[1974],{"type":26,"value":539},{"type":26,"value":541},{"type":21,"tag":521,"props":1977,"children":1978},{},[1979,1983],{"type":21,"tag":525,"props":1980,"children":1981},{},[1982],{"type":26,"value":549},{"type":26,"value":551},{"type":21,"tag":521,"props":1985,"children":1986},{},[1987,1991],{"type":21,"tag":525,"props":1988,"children":1989},{},[1990],{"type":26,"value":559},{"type":26,"value":561},{"type":21,"tag":521,"props":1993,"children":1994},{},[1995,1999],{"type":21,"tag":525,"props":1996,"children":1997},{},[1998],{"type":26,"value":569},{"type":26,"value":571},{"type":21,"tag":28,"props":2001,"children":2002},{},[2003],{"type":26,"value":576},{"title":7,"searchDepth":578,"depth":578,"links":2005},[2006,2007,2008,2013,2017,2021,2022,2026,2027],{"id":8,"depth":581,"text":8},{"id":35,"depth":581,"text":35},{"id":168,"depth":581,"text":168,"children":2009},[2010,2011,2012],{"id":179,"depth":578,"text":179},{"id":197,"depth":578,"text":197},{"id":213,"depth":578,"text":216},{"id":233,"depth":581,"text":236,"children":2014},[2015,2016],{"id":244,"depth":578,"text":244},{"id":258,"depth":578,"text":258},{"id":272,"depth":581,"text":272,"children":2018},[2019,2020],{"id":282,"depth":578,"text":282},{"id":296,"depth":578,"text":296},{"id":315,"depth":581,"text":315},{"id":334,"depth":581,"text":334,"children":2023},[2024,2025],{"id":344,"depth":578,"text":344},{"id":358,"depth":578,"text":358},{"id":372,"depth":581,"text":372},{"id":508,"depth":581,"text":508},1778574590500]