NC/CSV文件 在线查看及可视化展示

<template>
  <div>
    <!-- 预览对话框 -->
    <el-dialog title="预览" :visible.sync="dialogTableVisible" :before-close="handleDialogClose"
               :close-on-click-modal="false">
      <el-table :data="preList" height="300">
        <el-table-column property="name" label="名称"/>
        <el-table-column property="type" label="类型"/>
        <el-table-column fixed="right" label="操作" width="100">
          <template slot-scope="scope">
            <el-button @click="startDownload(scope.row)" type="text" size="small" :loading="loading">在线查看</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- NetCDF 数据展示对话框 -->
    <el-dialog title="NetCDF 数据展示" :visible.sync="dialogVisible" width="80%">
      <div class="nc-table">
        <!-- 表头 -->
        <div class="header-row">
          <div class="cell">变量</div>
          <div class="cell">维度</div>
          <div class="cell">属性</div>
          <div class="cell">类型</div>
          <div class="cell">大小</div>
        </div>
        <!-- 数据行 -->
        <RecycleScroller
          class="scroller"
          :items="tableData" 
          :item-size="50"
          key-field="id"
          v-slot="{ item }"
        >
          <div class="row">
            <div class="cell">{{ item.name }}</div>
            <div class="cell">{{ item.dimensions }}</div>
            <div class="cell">{{ item.attributes }}</div>
            <div class="cell">{{ item.type }}</div>
            <div class="cell">{{ item.size }}</div>
          </div>
        </RecycleScroller>
      </div>
      <template #footer>
        <el-button @click="dialogVisible = false">关闭</el-button>
      </template>
    </el-dialog>
    <!-- CSV 数据展示对话框 -->
    <el-dialog title="CSV 数据展示" :visible.sync="csvDialogVisible" width="80%">
      <div class="csv-table">
        <!-- 表头 -->
        <div class="header-row">
          <div v-for="column in csvTableColumns" :key="column.prop" class="header-cell">
            {{ column.label }}
          </div>
        </div>
        <!-- 数据行 -->
        <RecycleScroller
          class="scroller"
          :items="csvTableData"
          :item-size="50"
          key-field="id"
          v-slot="{ item }"
        >
          <div class="row">
            <div v-for="column in csvTableColumns" :key="column.prop" class="cell">
              {{ item[column.prop] }}
            </div>
          </div>
        </RecycleScroller>
      </div>
      <template #footer>
        <el-button @click="csvDialogVisible = false">关闭</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script>
import { downloadNcFile, previewDataset } from '@/api/model-user-client/datasetManager.js';
import { NetCDFReader } from 'netcdfjs';
import Papa from 'papaparse';
import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';

export default {
  name: "Preview",
  components: {
    RecycleScroller,
  },
  props: {
    row: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      dialogTableVisible: true, // 预览对话框可见性
      data: undefined, // 传入的行数据
      preList: [], // 预览文件列表
      chunkSize: 1024 * 1024, // 分块大小
      dialogVisible: false, // NetCDF 对话框可见性
      csvDialogVisible: false, // CSV 对话框可见性
      tableData: [], // NetCDF 表格数据
      csvTableData: [], // CSV 表格数据
      csvTableColumns: [], // CSV 表格列
      startByte: 0, // 下载起始字节
      allChunks: [], // 所有下载的分块数据,
      csvData: [], // CSV 数据
      loading: false, // 加载状态
    };
  },
  created() {
    this.data = this.row;
    this.getPreList();
  },
  methods: {
    /**
     * 开始下载文件并解析
     */
    startDownload(row) {
      console.log('开始下载文件并解析', row);
      if (this.loading) {
        return; // 如果正在加载,直接返回,不执行后续操作
      }
      this.loading = true; // 开始加载
      this.startByte = 0;
      this.allChunks = [];
      const fileExtension = row.name.split('.').pop().toLowerCase();
      if (fileExtension === 'csv') {
        this.downloadAndParseCSV(row);
      } else if (fileExtension === 'nc') {
        this.downloadAndParseNetCDF(row);
      } else {
        this.$message({ message: '不支持的文件格式', type: 'warning' });
        this.loading = false; // 停止加载
      }
    },

    /**
     * 下载分块数据
     */
    downloadChunk(row, callback) {
      const endByte = this.startByte + this.chunkSize - 1;
      const token = localStorage.getItem('token');
      const params = {
        datasetId: this.data.datasetId,
        version: this.data.version,
        fileName: row.name,
        range: `bytes=${this.startByte}-${endByte}`,
        token: token
      };
      downloadNcFile(params)
       .then(response => {
          if (response.data.content === '==END==') {
            callback();
            return;
          }
          try {
            const binaryString = atob(response.data.content);
            const uint8Array = new Uint8Array(binaryString.length);
            for (let i = 0; i < binaryString.length; i++) {
              uint8Array[i] = binaryString.charCodeAt(i);
            }
            this.allChunks.push(uint8Array);
            this.startByte = endByte + 1;
            this.downloadChunk(row, callback);
          } catch (error) {
            console.error('数据解码出错:', error);
            this.loading = false; // 停止加载
          }
        })
       .catch(error => {
          console.error('下载出错:', error);
          this.loading = false; // 停止加载
        });
    },

    /**
     * 下载并解析 CSV 文件
     */
    downloadAndParseCSV(row) {
      const onDownloadComplete = () => {
        // 所有分块数据下载完成,合并为一个 ArrayBuffer
        const mergedArray = new Uint8Array(this.allChunks.reduce((acc, arr) => acc + arr.length, 0));
        let offset = 0;
        for (const chunk of this.allChunks) {
          mergedArray.set(chunk, offset);
          offset += chunk.length;
        }
        const arrayBuffer = mergedArray.buffer;
        // 将 ArrayBuffer 转换为字符串
        const decoder = new TextDecoder('utf-8');
        const csvString = decoder.decode(arrayBuffer);
        this.parseCSV(csvString);
        this.csvDialogVisible = true;
        this.loading = false; // 停止加载
      };

      this.downloadChunk(row, onDownloadComplete);
    },

    /**
     * 解析 CSV 数据
     */
    parseCSV(csvData) {
      const lines = csvData.split('\n').filter(line => line.trim() !== ''); // 过滤空行
      const headers = lines[0].split(','); // 第一行为表头
      const tableData = lines.slice(1).map((line, index) => {
        const values = line.split(',');
        return {
          id: `csv-row-${index}`, // 添加唯一的 id
          ...headers.reduce((obj, header, index) => {
            obj[header] = values[index];
            return obj;
          }, {}),
        };
      });
      this.csvTableColumns = headers.map(header => ({ prop: header, label: header }));
      this.csvTableData = tableData;
      this.csvDialogVisible = true;
    },

    /**
     * 下载并解析 NetCDF 文件
     */
    downloadAndParseNetCDF(row) {
      const onDownloadComplete = () => {
        // 所有分块数据下载完成,合并为一个 ArrayBuffer
        const mergedArray = new Uint8Array(this.allChunks.reduce((acc, arr) => acc + arr.length, 0));
        let offset = 0;
        for (const chunk of this.allChunks) {
          mergedArray.set(chunk, offset);
          offset += chunk.length;
        }
        const arrayBuffer = mergedArray.buffer;
        // 检查文件头
        const header = new TextDecoder().decode(arrayBuffer.slice(0, 3));
        console.log('文件头:', header);
        if (header === 'CDF') {
          // 处理 NetCDF v3.x 文件
          try {
            const nc = new NetCDFReader(arrayBuffer);
            console.log(nc, 'nc');
            const allVariables = nc.variables;
            const allDimensions = nc.dimensions;

            this.tableData = allVariables.map((variable, index) => {
              const dimensionsContent = [];
              variable.dimensions.forEach(dimIndex => {
                const dim = allDimensions[dimIndex];
                dimensionsContent.push(dim.name);
              });
              return {
                id: `nc-row-${index}`, // 添加唯一的 id
                name: variable.name,
                dimensions: dimensionsContent.join(', '),
                attributes: '',
                type: variable.type,
                size: variable.size,
              };
            });
            this.dialogVisible = true;
            this.loading = false; // 停止加载
          } catch (error) {
            console.error('解析 NetCDF v3.x 文件出错:', error);
            this.$message({ message: '解析 NetCDF v3.x 文件出错', type: 'warning' });
            this.loading = false;
          }
        } else if (header === 'HDF') {
          // 处理 NetCDF 4 文件
          try {
            const nc = new NetCDFReader(arrayBuffer);
            const rootGroup = nc.get('/');
            const variables = [];
            const visitGroup = (group) => {
              group.variables.forEach((varName) => {
                const variable = group.get(varName);
                const dimensionsContent = [];
                variable.dimensions.forEach(dimIndex => {
                  const dim = group.dimensions[dimIndex];
                  dimensionsContent.push(dim.name);
                });
                const attributes = [];
                variable.attributes.forEach((attr) => {
                  attributes.push({
                    name: attr.name,
                    value: attr.value,
                    type: attr.type
                  });
                });
                variables.push({
                  id: `nc-row-${variables.length}`, // 添加唯一的 id
                  name: variable.name,
                  dimensions: dimensionsContent.join(', '),
                  attributes: '',
                  type: variable.type,
                  size: variable.size
                });
              });
              group.groups.forEach((subGroupName) => {
                visitGroup(group.get(subGroupName));
              });
            };
            visitGroup(rootGroup);
            this.tableData = variables;
            this.dialogVisible = true;
            this.loading = false;
          } catch (error) {
            console.error('解析 NetCDF 4 文件出错:', error);
            this.$message({ message: '解析 NetCDF 4 文件出错', type: 'warning' });
            this.loading = false;
          }
        } else {
          console.error('不是有效的 NetCDF 文件,不支持的文件头:', header);
          this.$message({ message: '不是有效的 NetCDF 文件', type: 'warning' });
          this.loading = false;
        }
      };
      this.downloadChunk(row, onDownloadComplete);
    },

    /**
     * 获取预览文件列表
     */
    getPreList() {
      const param = { datasetId: this.data.datasetId, version: this.data.version };
      previewDataset(param).then(response => {
        if (response.success) {
          this.preList = response.data.files;
        } else {
          this.$message({ message: this.getErrorMsg(response.error.subcode), type: 'warning' });
        }
      });
    },

    /**
     * 关闭对话框
     */
    handleDialogClose() {
      this.$emit('close', false);
    },
    // 这里假设 getErrorMsg 方法在其他地方有定义,如果没有定义需要补充实现
    getErrorMsg(subcode) {
      // 可以根据 subcode 返回具体的错误信息
      return '获取预览文件列表出错';
    }
  },
};
</script>

<style scoped>
.nc-table,
.csv-table {
  width: 100%;
  position: relative;
  /* 确保 el-loading 可以正确定位 */
}

.scroller {
  height: 400px; /* 设置虚拟列表的高度 */
}

.header-row {
  display: flex;
  border-bottom: 1px solid #eee;
  padding: 10px;
  font-weight: bold;
  background-color: #f5f7fa;
}

.header-cell,
.cell {
  flex: 1;
  padding: 0 10px;
}

.row {
  display: flex;
  border-bottom: 1px solid #eee;
  padding: 10px;
}
</style>

<template>

  <div>

    <!-- 预览对话框 -->

    <el-dialog title="预览" :visible.sync="dialogTableVisible" :before-close="handleDialogClose"

               :close-on-click-modal="false">

      <el-table :data="preList" height="300">

        <el-table-column property="name" label="名称"/>

        <el-table-column property="type" label="类型"/>

        <el-table-column fixed="right" label="操作" width="100">

          <template slot-scope="scope">

            <el-button @click="startDownload(scope.row)" type="text" size="small" :loading="loading">在线查看</el-button>

          </template>

        </el-table-column>

      </el-table>

    </el-dialog>

    <!-- NetCDF 数据展示对话框 -->

    <el-dialog title="NetCDF 数据展示" :visible.sync="dialogVisible" width="80%">

      <div class="nc-table">

        <!-- 表头 -->

        <div class="header-row">

          <div class="cell">变量</div>

          <div class="cell">维度</div>

          <div class="cell">属性</div>

          <div class="cell">类型</div>

          <div class="cell">大小</div>

        </div>

        <!-- 数据行 -->

        <RecycleScroller

          class="scroller"

          :items="tableData"

          :item-size="50"

          key-field="id"

          v-slot="{ item }"

        >

          <div class="row">

            <div class="cell">{{ item.name }}</div>

            <div class="cell">{{ item.dimensions }}</div>

            <div class="cell">{{ item.attributes }}</div>

            <div class="cell">{{ item.type }}</div>

            <div class="cell">{{ item.size }}</div>

          </div>

        </RecycleScroller>

      </div>

      <template #footer>

        <el-button @click="dialogVisible = false">关闭</el-button>

      </template>

    </el-dialog>

    <!-- CSV 数据展示对话框 -->

    <el-dialog title="CSV 数据展示" :visible.sync="csvDialogVisible" width="80%">

      <div class="csv-table">

        <!-- 表头 -->

        <div class="header-row">

          <div v-for="column in csvTableColumns" :key="column.prop" class="header-cell">

            {{ column.label }}

          </div>

        </div>

        <!-- 数据行 -->

        <RecycleScroller

          class="scroller"

          :items="csvTableData"

          :item-size="50"

          key-field="id"

          v-slot="{ item }"

        >

          <div class="row">

            <div v-for="column in csvTableColumns" :key="column.prop" class="cell">

              {{ item[column.prop] }}

            </div>

          </div>

        </RecycleScroller>

      </div>

      <template #footer>

        <el-button @click="csvDialogVisible = false">关闭</el-button>

      </template>

    </el-dialog>

  </div>

</template>

<script>

import { downloadNcFile, previewDataset } from '@/api/model-user-client/datasetManager.js';

import { NetCDFReader } from 'netcdfjs';

import Papa from 'papaparse';

import { RecycleScroller } from 'vue-virtual-scroller';

import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';

export default {

  name: "Preview",

  components: {

    RecycleScroller,

  },

  props: {

    row: {

      type: Object,

      default: () => ({})

    }

  },

  data() {

    return {

      dialogTableVisible: true, // 预览对话框可见性

      data: undefined, // 传入的行数据

      preList: [], // 预览文件列表

      chunkSize: 1024 * 1024, // 分块大小

      dialogVisible: false, // NetCDF 对话框可见性

      csvDialogVisible: false, // CSV 对话框可见性

      tableData: [], // NetCDF 表格数据

      csvTableData: [], // CSV 表格数据

      csvTableColumns: [], // CSV 表格列

      startByte: 0, // 下载起始字节

      allChunks: [], // 所有下载的分块数据,

      csvData: [], // CSV 数据

      loading: false, // 加载状态

    };

  },

  created() {

    this.data = this.row;

    this.getPreList();

  },

  methods: {

    /**

     * 开始下载文件并解析

     */

    startDownload(row) {

      console.log('开始下载文件并解析', row);

      if (this.loading) {

        return; // 如果正在加载,直接返回,不执行后续操作

      }

      this.loading = true; // 开始加载

      this.startByte = 0;

      this.allChunks = [];

      const fileExtension = row.name.split('.').pop().toLowerCase();

      if (fileExtension === 'csv') {

        this.downloadAndParseCSV(row);

      } else if (fileExtension === 'nc') {

        this.downloadAndParseNetCDF(row);

      } else {

        this.$message({ message: '不支持的文件格式', type: 'warning' });

        this.loading = false; // 停止加载

      }

    },

    /**

     * 下载分块数据

     */

    downloadChunk(row, callback) {

      const endByte = this.startByte + this.chunkSize - 1;

      const token = localStorage.getItem('token');

      const params = {

        datasetId: this.data.datasetId,

        version: this.data.version,

        fileName: row.name,

        range: `bytes=${this.startByte}-${endByte}`,

        token: token

      };

      downloadNcFile(params)

       .then(response => {

          if (response.data.content === '==END==') {

            callback();

            return;

          }

          try {

            const binaryString = atob(response.data.content);

            const uint8Array = new Uint8Array(binaryString.length);

            for (let i = 0; i < binaryString.length; i++) {

              uint8Array[i] = binaryString.charCodeAt(i);

            }

            this.allChunks.push(uint8Array);

            this.startByte = endByte + 1;

            this.downloadChunk(row, callback);

          } catch (error) {

            console.error('数据解码出错:', error);

            this.loading = false; // 停止加载

          }

        })

       .catch(error => {

          console.error('下载出错:', error);

          this.loading = false; // 停止加载

        });

    },

    /**

     * 下载并解析 CSV 文件

     */

    downloadAndParseCSV(row) {

      const onDownloadComplete = () => {

        // 所有分块数据下载完成,合并为一个 ArrayBuffer

        const mergedArray = new Uint8Array(this.allChunks.reduce((acc, arr) => acc + arr.length, 0));

        let offset = 0;

        for (const chunk of this.allChunks) {

          mergedArray.set(chunk, offset);

          offset += chunk.length;

        }

        const arrayBuffer = mergedArray.buffer;

        // 将 ArrayBuffer 转换为字符串

        const decoder = new TextDecoder('utf-8');

        const csvString = decoder.decode(arrayBuffer);

        this.parseCSV(csvString);

        this.csvDialogVisible = true;

        this.loading = false; // 停止加载

      };

      this.downloadChunk(row, onDownloadComplete);

    },

    /**

     * 解析 CSV 数据

     */

    parseCSV(csvData) {

      const lines = csvData.split('\n').filter(line => line.trim() !== ''); // 过滤空行

      const headers = lines[0].split(','); // 第一行为表头

      const tableData = lines.slice(1).map((line, index) => {

        const values = line.split(',');

        return {

          id: `csv-row-${index}`, // 添加唯一的 id

          ...headers.reduce((obj, header, index) => {

            obj[header] = values[index];

            return obj;

          }, {}),

        };

      });

      this.csvTableColumns = headers.map(header => ({ prop: header, label: header }));

      this.csvTableData = tableData;

      this.csvDialogVisible = true;

    },

    /**

     * 下载并解析 NetCDF 文件

     */

    downloadAndParseNetCDF(row) {

      const onDownloadComplete = () => {

        // 所有分块数据下载完成,合并为一个 ArrayBuffer

        const mergedArray = new Uint8Array(this.allChunks.reduce((acc, arr) => acc + arr.length, 0));

        let offset = 0;

        for (const chunk of this.allChunks) {

          mergedArray.set(chunk, offset);

          offset += chunk.length;

        }

        const arrayBuffer = mergedArray.buffer;

        // 检查文件头

        const header = new TextDecoder().decode(arrayBuffer.slice(0, 3));

        console.log('文件头:', header);

        if (header === 'CDF') {

          // 处理 NetCDF v3.x 文件

          try {

            const nc = new NetCDFReader(arrayBuffer);

            console.log(nc, 'nc');

            const allVariables = nc.variables;

            const allDimensions = nc.dimensions;

            this.tableData = allVariables.map((variable, index) => {

              const dimensionsContent = [];

              variable.dimensions.forEach(dimIndex => {

                const dim = allDimensions[dimIndex];

                dimensionsContent.push(dim.name);

              });

              return {

                id: `nc-row-${index}`, // 添加唯一的 id

                name: variable.name,

                dimensions: dimensionsContent.join(', '),

                attributes: '',

                type: variable.type,

                size: variable.size,

              };

            });

            this.dialogVisible = true;

            this.loading = false; // 停止加载

          } catch (error) {

            console.error('解析 NetCDF v3.x 文件出错:', error);

            this.$message({ message: '解析 NetCDF v3.x 文件出错', type: 'warning' });

            this.loading = false;

          }

        } else if (header === 'HDF') {

          // 处理 NetCDF 4 文件

          try {

            const nc = new NetCDFReader(arrayBuffer);

            const rootGroup = nc.get('/');

            const variables = [];

            const visitGroup = (group) => {

              group.variables.forEach((varName) => {

                const variable = group.get(varName);

                const dimensionsContent = [];

                variable.dimensions.forEach(dimIndex => {

                  const dim = group.dimensions[dimIndex];

                  dimensionsContent.push(dim.name);

                });

                const attributes = [];

                variable.attributes.forEach((attr) => {

                  attributes.push({

                    name: attr.name,

                    value: attr.value,

                    type: attr.type

                  });

                });

                variables.push({

                  id: `nc-row-${variables.length}`, // 添加唯一的 id

                  name: variable.name,

                  dimensions: dimensionsContent.join(', '),

                  attributes: '',

                  type: variable.type,

                  size: variable.size

                });

              });

              group.groups.forEach((subGroupName) => {

                visitGroup(group.get(subGroupName));

              });

            };

            visitGroup(rootGroup);

            this.tableData = variables;

            this.dialogVisible = true;

            this.loading = false;

          } catch (error) {

            console.error('解析 NetCDF 4 文件出错:', error);

            this.$message({ message: '解析 NetCDF 4 文件出错', type: 'warning' });

            this.loading = false;

          }

        } else {

          console.error('不是有效的 NetCDF 文件,不支持的文件头:', header);

          this.$message({ message: '不是有效的 NetCDF 文件', type: 'warning' });

          this.loading = false;

        }

      };

      this.downloadChunk(row, onDownloadComplete);

    },

    /**

     * 获取预览文件列表

     */

    getPreList() {

      const param = { datasetId: this.data.datasetId, version: this.data.version };

      previewDataset(param).then(response => {

        if (response.success) {

          this.preList = response.data.files;

        } else {

          this.$message({ message: this.getErrorMsg(response.error.subcode), type: 'warning' });

        }

      });

    },

    /**

     * 关闭对话框

     */

    handleDialogClose() {

      this.$emit('close', false);

    },

    // 这里假设 getErrorMsg 方法在其他地方有定义,如果没有定义需要补充实现

    getErrorMsg(subcode) {

      // 可以根据 subcode 返回具体的错误信息

      return '获取预览文件列表出错';

    }

  },

};

</script>

<style scoped>

.nc-table,

.csv-table {

  width: 100%;

  position: relative;

  /* 确保 el-loading 可以正确定位 */

}

.scroller {

  height: 400px; /* 设置虚拟列表的高度 */

}

.header-row {

  display: flex;

  border-bottom: 1px solid #eee;

  padding: 10px;

  font-weight: bold;

  background-color: #f5f7fa;

}

.header-cell,

.cell {

  flex: 1;

  padding: 0 10px;

}

.row {

  display: flex;

  border-bottom: 1px solid #eee;

  padding: 10px;

}

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值