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

NextJS仓库规划应用中DWG转SVG后无法在网格正常显示的问题求助

NextJS仓库规划应用中DWG转SVG后无法在网格正常显示的问题求助

我现在快急死了!正在开发一个基于NextJS前端+AdonisJS后端的仓库规划应用,用户能在网格上添加地板、墙壁这类元素,还能移动、缩放、拼接它们。原本一切都运行得好好的,直到用户提出要支持DWG文件导入的需求——上传DWG文件后,要能把它当作普通元素放在网格上,支持移动、缩放等操作。

我已经做了一些工作:DWG文件能上传到后端,后端也能把它转成SVG返回给前端,但就是没法在网格上正常显示SVG内容,现在显示的是这样的效果:

导入的DWG文件显示效果

我把前端和后端的代码都贴出来,希望有人能帮我看看这到底能不能实现,问题出在哪。对了,DWG文件大概5MB左右。


后端代码:import_controller.ts

const execFileAsync = promisify(execFile)

export default class ImportController {
  public async importDwg({ request, response }: HttpContext) {
    try {
      const file = request.file('file')
      if (!file) return response.badRequest({ message: 'No file uploaded' })

      const tmpDir = path.join(process.cwd(), 'tmp')
      await fs.mkdir(tmpDir, { recursive: true })

      const timestamp = Date.now()
      const dwgPath = path.join(tmpDir, `${timestamp}_${file.clientName}`)

      await file.move(tmpDir, { name: path.basename(dwgPath) })

      // Convert DWG to SVG
      const dwg2svgPath = '/usr/local/bin/dwg2svg'
      const { stdout, stderr } = await execFileAsync(dwg2svgPath, [dwgPath], {
        maxBuffer: 1024 * 1024 * 64, // 64 MB
      })

      console.log(`dwg2svg stderr: ${stderr}`)

      let svgString = stdout

      // Fix malformed width/height line
      svgString = svgString.replace(/width="([^"]+)\s+height="([^"]+)"/, 'width="$1" height="$2"');

      // Ensure viewBox exists
      const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/)
      if (!viewBoxMatch) {
        const width = svgString.match(/width="([^"]+)"/)?.[1] ?? '2000'
        const height = svgString.match(/height="([^"]+)"/)?.[1] ?? '1000'
        // svgString = svgString.replace(/<svg([^>]*)>/, `<svg$1 viewBox="0 0 ${width} ${height}">`)
      }

      // Constrain to iframe view
      svgString = svgString
        .replace(/width="[^"]+"/, 'width="100%"')
        .replace(/height="[^"]+"/, 'height="100%"')

      await fs.writeFile(path.join(tmpDir, 'debug_output.svg'), svgString)

      const base64Svg = `data:image/svg+xml;base64,${Buffer.from(svgString).toString('base64')}`

      return response.ok({
        message: 'DWG converted successfully',
        image: base64Svg,
        elements: [
          {
            id: Date.now(),
            type: 'dwg',
            x: 0,
            y: 0,
            width: 800,
            height: 600,
            content: base64Svg,
            originalFileName: file.clientName,
          }
        ]
      })

    } catch (error: any) {
      console.error('Error importing DWG:', error)
      return response.internalServerError({
        message: 'Failed to import DWG',
        error: error.message,
      })
    }
  }
}

前端代码:warehouse-grid.tsx

"use client";

export const WarehouseGrid = forwardRef<
  { fitAllElementsToView: () => void },
  WarehouseGridProps
>(function WarehouseGrid(
  {
    selectedTool,
    setSelectedTool,
    lang = "sk",
    is3DMode,
    elements,
    onElementsChange,
  }: WarehouseGridProps,
  ref
) {
  return (
    <div className="flex flex-col h-full">
      {/* Controls section - always visible in both modes */}
      <div className="flex items-center gap-4 p-2 bg-white border-b">
        <div className="flex items-center gap-2">
          <Button
            variant={selectedTool === "move" ? "default" : "ghost"}
            onClick={() => setSelectedTool("move")}
            size="icon"
            title={t(lang, "moveTool")}
          >
            <Move className="h-4 w-4" />
          </Button>
          <Button
            variant={selectedTool === "resize" ? "default" : "ghost"}
            onClick={() => setSelectedTool("resize")}
            size="icon"
            title={t(lang, "resizeTool")}
          >
            <Maximize className="h-4 w-4" />
          </Button>
          <Button
            variant={selectedTool === "pan" ? "default" : "ghost"}
            onClick={() => setSelectedTool("pan")}
            size="icon"
            title={t(lang, "panTool")}
          >
            <Hand className="h-4 w-4" />
          </Button>
          <Separator orientation="vertical" className="h-4" />
          <Button
            variant="outline"
            size="icon"
            title={t(lang, "zoomOut")}
            onClick={handleZoomOut}
          >
            <Minus className="h-4 w-4" />
          </Button>
          <Button
            variant="outline"
            size="icon"
            title={t(lang, "zoomIn")}
            onClick={handleZoomIn}
          >
            <Plus className="h-4 w-4" />
          </Button>
          <Button
            variant="outline"
            size="icon"
            title={t(lang, "resetZoom")}
            onClick={handleZoomReset}
          >
            <Maximize2 className="h-4 w-4" />
          </Button>
          <Button
            variant="ghost"
            size="icon"
            title={t(lang, "fitToScreen")}
            onClick={fitAllElementsToView}
          >
            <Maximize2 className="h-4 w-4" />
          </Button>
        </div>
      </div>
  
      {/* Main content area */}
      <div className="flex-1 relative">
        {is3DMode ? (
          // 3D Mode
          <div ref={containerRef} className="w-full h-full" />
        ) : (
          // 2D Mode
          <div className="flex h-full">
            {/* Grid container */}
            <div
              ref={gridRef}
              className={cn(
                "relative flex-1 bg-gray-50 overflow-hidden",
                {
                  "cursor-grab": selectedTool === "pan",
                  "cursor-grabbing": isDraggingViewport,
                  "cursor-default": selectedTool === "move" || selectedTool === "resize",
                }
              )}
              onClick={handleGridClick}
              onMouseDown={handleMouseDown}
              onMouseMove={handleMouseMove}
              onMouseUp={handleMouseUp}
              onMouseLeave={handleMouseUp}
              onDragOver={handleDragOver}
              onDrop={handleDrop}
            >
              <div
                style={{
                  transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.scale})`,
                  transformOrigin: "0 0",
                  width: "100%",
                  height: "100%",
                  position: "absolute",
                }}
              >
                {renderGrid()}
                {elements.map((element) => {
                  return (
                    <DraggableWithoutFindDOMNode
                      key={element.id}
                      position={{ x: element.x, y: element.y }}
                      onStop={(e, data) => handleDragStop(element.id, e, data)}
                      bounds="parent"
                      disabled={selectedTool !== "move"}
                      handle=".drag-handle"
                    >
                      <ResizableBox
                        width={element.width}
                        height={element.height}
                        onResizeStop={(e, data) => handleResizeStop(element.id, e, data)}
                        draggableOpts={{ disabled: selectedTool !== "resize" }}
                        handle={
                          <div className="absolute right-0 bottom-0 w-4 h-4 bg-blue-500 cursor-se-resize rounded-bl" />
                        }
                      >
                        <div
                          className={cn(
                            "relative border-2 drag-handle",
                            selectedElementIds.includes(element.id)
                              ? "border-blue-500"
                              : "border-gray-200"
                          )}
                          onClick={(e) => handleElementClick(e, element.id)}
                        >
                          {renderElement(element)}
                        </div>
                      </ResizableBox>
                    </DraggableWithoutFindDOMNode>
                  );
                })}
              </div>
            </div>
  
            {/* Properties panel */}
            {selectedElementIds.length === 1 && (
              <div className="w-80 border-l border-gray-200">
                {/* 原代码此处内容未完整粘贴 */}
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
});

备注:内容来源于stack exchange,提问作者Marek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 19:48:01