<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>