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

如何在Angular中为Forge Viewer创建导出数据为CSV格式的下载按钮

Forge Viewer SVF属性导出CSV实现方案

基于你现有的ng2-adsk-forge-viewer扩展代码,只需新增属性拉取、CSV格式转换、交互触发三个模块的逻辑即可完成需求,修改后的完整代码如下:

import { Extension } from 'ng2-adsk-forge-viewer';
// 如果你项目用Angular HttpClient,在这里导入
// import { HttpClient } from '@angular/common/http';

declare const THREE: any;

export class ForgeExtension extends Extension {
  // 扩展名称固定
  public static extensionName: string = 'ForgeExtension';

  private subToolbar: Autodesk.Viewing.UI.ToolBar;
  private onToolbarCreatedBinded: any;
  // 如果你用HttpClient,在这里注入
  // constructor(viewer, options, private http: HttpClient) {
  //   super(viewer, options);
  // }

  public activate() {
    return true;
  }

  public deactivate() {
    return true;
  }

  public load() {
    console.log('ForgeExtension 已加载!');

    this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, (e) => {
      if (e.dbIdArray.length) {
        const dbId = e.dbIdArray[0];
        this.viewer.setThemingColor(dbId, new THREE.Vector4(0, 1, 1, 1));
      }
    });

    // 初始化工具栏
    if (this.viewer.toolbar) {
      this.createUI();
    } else {
      this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
      this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
    }

    return true;
  }

  public unload() {
    if (this.subToolbar) {
      this.viewer.toolbar.removeControl(this.subToolbar);
      this.subToolbar = null;
    }

    console.log('ForgeExtension 已卸载.');
    return true;
  }

  public onToolbarCreated() {
    this.viewer.removeEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
    this.onToolbarCreatedBinded = null;
    this.createUI();
  }

  private createUI() {
    // 视角切换按钮(保留原有功能)
    const button1 = new Autodesk.Viewing.UI.Button('my-view-front-button');
    button1.onClick = e => this.setViewCube('front');
    button1.addClass('my-view-front-button');
    button1.setToolTip('前视图');

    const button2 = new Autodesk.Viewing.UI.Button('my-view-back-button');
    button2.onClick = e => this.setViewCube('back');
    button2.addClass('my-view-back-button');
    button2.setToolTip('后视图');

    // 新增CSV导出按钮
    const exportButton = new Autodesk.Viewing.UI.Button('export-csv-button');
    exportButton.onClick = async e => await this.handleExportCsv();
    exportButton.addClass('export-csv-button');
    exportButton.setToolTip('导出属性为CSV');

    // 组装工具栏
    this.subToolbar = new Autodesk.Viewing.UI.ControlGroup('my-custom-view-toolbar');
    this.subToolbar.addControl(button1);
    this.subToolbar.addControl(button2);
    this.subToolbar.addControl(exportButton);

    this.viewer.toolbar.addControl(this.subToolbar);
  }

  private setViewCube(orientation: 'front' | 'back') {
    const ext = (this.viewer.getExtension('Autodesk.ViewCubeUi') as any);
    ext.setViewCube(orientation);
  }

  // 核心:获取所有带属性的模型元素
  private async getAllElementProperties(): Promise<any[]> {
    const result = [];
    const instanceTree = this.viewer.model.getInstanceTree();
    const rootId = instanceTree.getRootId();
    const allDbIds = [];
    
    // 遍历所有元素dbId
    instanceTree.enumNodeChildren(rootId, (dbId) => {
      allDbIds.push(dbId);
    }, true);

    // 批量获取属性,过滤无属性元素
    for (const dbId of allDbIds) {
      const props = await new Promise((resolve) => {
        this.viewer.getProperties(dbId, resolve, () => resolve(null));
      });
      if (props?.properties) {
        // 把属性数组转成对象方便后续处理
        const propObj = { dbId: dbId, name: props.name };
        props.properties.forEach(p => {
          propObj[p.displayName] = p.displayValue;
        });
        result.push(propObj);
      }
    }
    return result;
  }

  // 转换为CSV格式
  private convertToCsv(data: any[]): string {
    if (!data.length) return '';
    // 提取所有表头字段
    const headers = Array.from(new Set(data.flatMap(item => Object.keys(item))));
    // 处理特殊字符转义
    const escapeCsv = (val) => {
      if (val == null) return '';
      const str = String(val);
      return str.includes(',') || str.includes('"') || str.includes('\n') ? `"${str.replace(/"/g, '""')}"` : str;
    };
    // 拼接CSV内容
    return [
      headers.join(','),
      ...data.map(row => headers.map(h => escapeCsv(row[h])).join(','))
    ].join('\n');
  }

  // 导出按钮点击事件
  private async handleExportCsv() {
    const propList = await this.getAllElementProperties();
    const csvContent = this.convertToCsv(propList);

    // 功能1:直接下载CSV到本地
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', `模型属性_${Date.now()}.csv`);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    // 功能2:上传CSV到后端,不需要可以注释
    const formData = new FormData();
    formData.append('csvFile', blob, `模型属性_${Date.now()}.csv`);
    // 替换为你自己的后端接口地址
    fetch('/api/upload-csv', {
      method: 'POST',
      body: formData
    }).then(res => res.json()).then(res => {
      console.log('上传成功', res);
    }).catch(err => {
      console.error('上传失败', err);
    });
    // 如果用Angular HttpClient,替换为以下写法
    // this.http.post('/api/upload-csv', formData).subscribe(res => {
    //   console.log('上传成功', res);
    // })
  }
}

注意事项

  • 如果需要只导出指定字段,可以在getAllElementProperties方法中对生成的属性对象做裁剪,删除不需要的字段
  • 大模型的属性拉取会有一定耗时,可以自行添加loading交互优化用户体验
  • 后端上传部分可以根据你项目的接口规范调整参数格式,支持直接传CSV字符串或者FormData文件格式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 01:54:00