{"record":{"id":"aebd738197235edf","repo":"datawhalechina/hello-agents","slug":"error-aebd73","errorCode":null,"errorMessage":"未找到内容元素","messagePattern":"未找到内容元素","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"code/chapter13/helloagents-trip-planner/frontend/src/views/Result.vue","lineNumber":502,"sourceCode":"}\n\n// 图片加载失败时的处理\nconst handleImageError = (event: Event) => {\n  const img = event.target as HTMLImageElement\n  // 使用灰色占位图\n  img.src = 'data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"400\" height=\"300\"%3E%3Crect width=\"400\" height=\"300\" fill=\"%23f0f0f0\"/%3E%3Ctext x=\"50%25\" y=\"50%25\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-family=\"sans-serif\" font-size=\"18\" fill=\"%23999\"%3E图片加载失败%3C/text%3E%3C/svg%3E'\n}\n\n\n\n// 导出为图片\nconst exportAsImage = async () => {\n  try {\n    message.loading({ content: '正在生成图片...', key: 'export', duration: 0 })\n\n    const element = document.querySelector('.main-content') as HTMLElement\n    if (!element) {\n      throw new Error('未找到内容元素')\n    }\n\n    // 创建一个独立的容器\n    const exportContainer = document.createElement('div')\n    exportContainer.style.width = element.offsetWidth + 'px'\n    exportContainer.style.backgroundColor = '#f5f7fa'\n    exportContainer.style.padding = '20px'\n\n    // 复制所有内容\n    exportContainer.innerHTML = element.innerHTML\n\n    // 处理地图截图\n    const mapContainer = document.getElementById('amap-container')\n    if (mapContainer && map) {\n      const mapCanvas = mapContainer.querySelector('canvas')\n      if (mapCanvas) {\n        const mapSnapshot = mapCanvas.toDataURL('image/png')\n        const exportMapContainer = exportContainer.querySelector('#amap-container')","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/code/chapter13/helloagents-trip-planner/frontend/src/views/Result.vue#L484-L520","documentation":"Thrown by exportAsImage in the trip-planner Result view when document.querySelector('.main-content') returns null before html-to-image/PDF capture. It means the DOM node the exporter clones (innerHTML is copied into an offscreen container) is not present at call time — either the route/component never rendered it, the CSS class was renamed, or the export ran while v-if kept the node out of the DOM.","triggerScenarios":"Clicking 导出为图片 before the result data loads (the .main-content block is behind v-if=\"result\"), after route navigation unmounts Result.vue, or after a refactor renames the wrapper class in the template while the exporter still queries '.main-content'. Also occurs when export is triggered from a teleported popover where document-level querySelector runs against a stale document.","commonSituations":"Vue 3 SFC with conditional rendering (v-if) around the exported region; Tailwind/SCSS refactor renaming the container class; running export in a unit test (jsdom) where the component is never mounted; export button enabled during loading state.","solutions":["Verify the class name in Result.vue's template still matches '.main-content' exactly (grep the template block).","Disable the export button until the result is loaded and rendered (bind :disabled=\"!result\" or guard on the ref).","Prefer a template ref over a global class query: const contentRef = ref<HTMLElement|null>(null) and querySelector on the component root.","If v-if guards the block, switch to v-show or await nextTick() after data arrival before exporting.","In tests, mount the component properly or mock document.querySelector before invoking exportAsImage."],"exampleFix":"// before\nconst element = document.querySelector('.main-content') as HTMLElement\nif (!element) {\n  throw new Error('未找到内容元素')\n}\n\n// after\nconst contentRef = ref<HTMLElement | null>(null)\n// template: <div class=\"main-content\" ref=\"contentRef\">\nconst exportAsImage = async () => {\n  await nextTick()\n  const element = contentRef.value\n  if (!element) {\n    message.warning('结果尚未加载，无法导出')\n    return\n  }\n  // ...\n}","handlingStrategy":"validation","validationCode":"// Run before exporting: node must exist and have layout.\nfunction canExport(el) {\n  return el instanceof HTMLElement && el.offsetWidth > 0 && el.innerHTML.trim().length > 0;\n}\nif (!canExport(contentRef.value)) { message.warning('结果未就绪'); return; }","typeGuard":"function isHTMLElement(v) {\n  return v instanceof HTMLElement;\n}","tryCatchPattern":"try {\n  const el = contentRef.value;\n  if (!el) throw new Error('未找到内容元素');\n  await exportAsImage(el);\n} catch (e) {\n  message.error(`导出失败: ${e.message}`);\n} finally {\n  message.destroy('export');\n}","preventionTips":["Bind the exported region with a template ref instead of a global CSS-class query.","Disable export controls until the result data is loaded and rendered.","await nextTick() after data arrival before any DOM-dependent export."],"tags":["vue","dom","export","frontend","null-check"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}