You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中用html2canvas截图后无法更新后端Page的screenshot字段

问题:截图上传后端后Page数据未更新screenshot字段

我在React项目中用html2canvas对<TemplateRealEstate/>组件内的<FormList/>(展示房屋信息的列表组件)截图,因需在非父子关系组件中展示,Context方案未生效,故打算将截图上传至后端存储后再展示。页面路由为http://localhost:3000/pages/:pageId,创建Page时screenshot字段初始值为undefined。创建Page和模板后,点击按钮触发截图操作,前端Axios请求返回状态码201,但后端返回的Page数据中仍未添加或更新screenshot字段,尝试多种Content-Type均无效。

相关代码

前端TemplateRealEstate.js

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import domtoimage from "dom-to-image";
import html2canvas from "html2canvas";

import FormList from "../components/FormList";

import "./TemplateRealEstate.css";

const TemplateRealEstate = () => {
  const [loadedTemplates, setLoadedTemplates] = useState([]);
  const [screenshot, setScreenshot] = useState(null);
  const [screenshotFile, setScreenshotFile] = useState(null);
  const [screenshotUrl, setScreenshotUrl] = useState(null);

  const pageId = useParams().pageId;

  // 将data URI转为Blob对象
  const dataURItoBlob = (dataURI) => {
    const binaryString = window.atob(dataURI.split(",")[1]);
    const mimeType = dataURI.split(",")[0].split(":")[1].split(";"[0]);
    const arrayBuffer = new ArrayBuffer(binaryString.length);
    const uint8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i < binaryString.length; i++) {
      uint8Array[i] = binaryString.charCodeAt(i);
    }
    return new Blob([arrayBuffer], { type: mimeType });
  };

  const handleScreenshot = () => {
    const element = document.getElementById("my-element");
    html2canvas(element)
      .then((canvas) => {
        const dataURI = canvas.toDataURL();
        const blob = dataURItoBlob(dataURI);
        setScreenshotFile(blob);

        const formData = new FormData();
        formData.append("screenshot", blob, "screenshot.png");

        // 发送截图到后端
        axios
          .patch(`http://localhost:8080/api/pages/${pageId}`, formData, {
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
            },
          })
          .then((res) => {
            console.log("Successfully sent screenshot to server:", res);
            setScreenshot(formData);
          })
          .catch((err) => {
            console.error("Failed to send screenshot to server:", err);
          });
      })
      .catch((error) => {
        console.error("Failed to take screenshot:", error);
      });
  };

  useEffect(() => {
    if (screenshotFile) {
      const reader = new FileReader();
      reader.onload = (event) => {
        setScreenshotUrl(event.target.result);
      };
      reader.readAsDataURL(screenshotFile);
    }
  }, [screenshotFile]);

  return (
    <div>
      <div id="my-element">
        <FormList items={loadedTemplates} />
      </div>
      <button onClick={redirectToForm}>Add house</button>
      <button type="button" onClick={handleScreenshot}>
        Set screenshot
      </button>
      <button onClick={handleDelete}>Delete Page</button>
    </div>
  );
};

export default TemplateRealEstate;

后端Page控制器

// PATCH http://localhost:8080/api/pages/:pid
const updatePageById = async (req, res, next) => {
  const pageId = req.params.pid;
  const screenshot = req.file;

  try {
    page = await Page.findById(pageId);
  } catch (err) {
    const error = new HttpError(
      "Something went wrong, could not update a page with that ID.",
      500
    );
    return next(error);
  }

  if (screenshot) page.screenshot = screenshot;

  try {
    await page.save();
  } catch (err) {
    const error = new HttpError(
      "Something went wrong, could not update page",
      500
    );
    return next(error);
  }

  res.status(201).json({ page: page.toObject({ getters: true }) });
};

解决方案

1. 修复前端请求头

用FormData上传文件时,Axios会自动生成包含boundary的正确Content-Type,手动设置application/x-www-form-urlencoded会导致后端无法解析文件。删除请求头配置:

axios.patch(`http://localhost:8080/api/pages/${pageId}`, formData)
  .then(res => {
    console.log("Successfully sent screenshot to server:", res);
    setScreenshot(res.data.page.screenshot); // 存储后端返回的截图路径
  })
  .catch(err => {
    console.error("Failed to send screenshot to server:", err);
  });

2. 后端配置文件解析中间件

处理FormData文件需借助multer中间件(以Express为例):

  • 安装依赖:npm install multer
  • 配置路由和存储规则:
const multer = require('multer');
// 配置文件存储路径和命名规则
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/'); // 确保uploads文件夹已创建
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + '-' + file.originalname); // 避免文件名重复
  }
});
const upload = multer({ storage: storage });

// 在路由中添加中间件,指定接收名为screenshot的单文件
router.patch('/api/pages/:pid', upload.single('screenshot'), updatePageById);

3. 后端存储文件路径而非完整file对象

直接存储req.file会导致数据库无法序列化对象,应存储文件的访问路径:

const updatePageById = async (req, res, next) => {
  const pageId = req.params.pid;
  // 生成可访问的文件路径,根据后端部署情况调整
  const screenshotPath = req.file ? `/uploads/${req.file.filename}` : null;

  let page;
  try {
    page = await Page.findById(pageId);
  } catch (err) {
    const error = new HttpError(
      'Something went wrong, could not update a page with that ID.',
      500
    );
    return next(error);
  }

  if (screenshotPath) {
    page.screenshot = screenshotPath; // 存储路径
  }

  try {
    await page.save();
  } catch (err) {
    const error = new HttpError(
      'Something went wrong, could not update page',
      500
    );
    return next(error);
  }

  res.status(200).json({ page: page.toObject({ getters: true }) });
  // PATCH请求成功应返回200,201用于创建资源
};

4. 检查Page模型定义

确保MongoDB的Page模型中包含screenshot字段,类型为字符串:

const mongoose = require('mongoose');

const pageSchema = new mongoose.Schema({
  // 其他字段...
  screenshot: { type: String, default: undefined }
});

module.exports = mongoose.model('Page', pageSchema);

内容的提问来源于stack exchange,提问作者yuroni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 07:25:18