fix: 优化导入 SMAP 文件的逻辑,增加文件格式检查和错误提示,改善用户体验
This commit is contained in:
parent
ca69dbccf8
commit
53332fca93
@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, type Ref } from 'vue';
|
||||
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||
import type { UploadProps } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message, type UploadProps } from 'ant-design-vue';
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
@ -18,7 +17,6 @@ watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
// Reset state when modal is closed
|
||||
fileList.value = [];
|
||||
keepProperties.value = false;
|
||||
}
|
||||
@ -40,9 +38,17 @@ const handleCancel = () => {
|
||||
};
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
// Ensure only one file is in the list
|
||||
const fileName = file.name ?? '';
|
||||
const dotIndex = fileName.lastIndexOf('.');
|
||||
const ext = dotIndex >= 0 ? fileName.slice(dotIndex).toLowerCase() : '';
|
||||
|
||||
if (ext !== '.smap') {
|
||||
message.error('请上传 .smap 格式的文件');
|
||||
fileList.value = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
fileList.value = [file];
|
||||
// Prevent automatic upload
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
@ -50,7 +56,7 @@ const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="props.visible"
|
||||
title="从 SMAP 导入场景"
|
||||
title="导入 SMAP 文件"
|
||||
ok-text="开始导入"
|
||||
cancel-text="取消"
|
||||
:ok-button-props="{ disabled: !fileList || fileList.length === 0 }"
|
||||
@ -67,9 +73,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击或拖拽 SMAP 文件到此区域</p>
|
||||
<p class="ant-upload-hint">仅支持单个 .smap 文件的导入。</p>
|
||||
<p class="ant-upload-text">点击或将 SMAP 文件拖拽到此处上传</p>
|
||||
<p class="ant-upload-hint">仅支持上传扩展名为 .smap 的文件。</p>
|
||||
</a-upload-dragger>
|
||||
<a-checkbox v-model:checked="keepProperties" style="margin-top: 16px"> 保留原属性 </a-checkbox>
|
||||
<a-checkbox v-model:checked="keepProperties" style="margin-top: 16px">
|
||||
保留原有属性
|
||||
</a-checkbox>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
@ -2,11 +2,11 @@
|
||||
<a-modal :visible="visible" title="地图格式转换" @cancel="handleCancel" :footer="null" width="600px" height="600px">
|
||||
<div class="converter-container">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="选择转换类型">
|
||||
<a-form-item label="选择转换方向">
|
||||
<a-radio-group v-model:value="convertType" button-style="solid" style="width: 100%">
|
||||
<a-radio-button value="scene" style="width: 33.33%">SMAP转Scene</a-radio-button>
|
||||
<a-radio-button value="iray" style="width: 33.33%">SMAP转IRAY</a-radio-button>
|
||||
<a-radio-button value="scene-to-smap" style="width: 33.33%">Scene更新SMAP</a-radio-button>
|
||||
<a-radio-button value="scene" style="width: 33.33%">SMAP 转 Scene</a-radio-button>
|
||||
<a-radio-button value="iray" style="width: 33.33%">SMAP 转 IRAY</a-radio-button>
|
||||
<a-radio-button value="scene-to-smap" style="width: 33.33%">Scene 转 SMAP</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
@ -16,11 +16,12 @@
|
||||
@change="handleFile1Change"
|
||||
:file-list="fileList1"
|
||||
:max-count="1"
|
||||
:accept="file1Accept"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<inbox-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击或拖拽文件到这里上传</p>
|
||||
<p class="ant-upload-text">点击或将文件拖拽到此处上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-form-item>
|
||||
|
||||
@ -30,16 +31,17 @@
|
||||
@change="handleFile2Change"
|
||||
:file-list="fileList2"
|
||||
:max-count="1"
|
||||
:accept="file2Accept"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<inbox-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击或拖拽文件到这里上传</p>
|
||||
<p class="ant-upload-text">点击或将文件拖拽到此处上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-form-item>
|
||||
|
||||
<div v-if="convertType === 'iray'" class="iray-params">
|
||||
<p class="param-section-title">地图参数设置 (IRAY转换必填)</p>
|
||||
<p class="param-section-title">地图参数设置(SMAP 转 IRAY)</p>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="地图宽度 (mm)">
|
||||
@ -54,12 +56,12 @@
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="X最小值 (mm)">
|
||||
<a-form-item label="X 最小值 (mm)">
|
||||
<a-input-number v-model:value="irayParams.xAttrMin" placeholder="-46762" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Y最小值 (mm)">
|
||||
<a-form-item label="Y 最小值 (mm)">
|
||||
<a-input-number v-model:value="irayParams.yAttrMin" placeholder="-63362" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@ -68,14 +70,14 @@
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleConvert" :loading="loading" block>{{
|
||||
convertType === 'iray' ? '上传、转换并下载' : '上传并转换'
|
||||
convertType === 'iray' ? '上传并转换(返回压缩包)' : '上传并转换'
|
||||
}}</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="downloadUrl">
|
||||
<a-button type="primary" block @click="handleDownload">下载转换后的文件</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item label="转换结果" v-if="result">
|
||||
<a-textarea :value="result" :rows="8" read-only />
|
||||
<a-textarea :value="result" :rows="8" readonly />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
@ -86,27 +88,23 @@
|
||||
import { InboxOutlined } from '@ant-design/icons-vue';
|
||||
import http from '@core/http';
|
||||
import { message, type UploadChangeParam } from 'ant-design-vue';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
/**
|
||||
* 调用地图转换API
|
||||
* @param convertType 转换类型
|
||||
* @param formData 包含文件和参数的FormData
|
||||
* @returns 转换结果
|
||||
*/
|
||||
const convertMap = async (convertType: string, formData: FormData) => {
|
||||
import { computed, reactive, ref, watch, type Ref } from 'vue';
|
||||
|
||||
type ConversionType = 'scene' | 'iray' | 'scene-to-smap';
|
||||
|
||||
const convertMap = async (convertType: ConversionType, formData: FormData) => {
|
||||
let apiUrl = '';
|
||||
switch (convertType) {
|
||||
case 'iray':
|
||||
apiUrl = import.meta.env.VWED_API_BASE_URL + '/api/vwed-map-converter/smap-to-iray';
|
||||
apiUrl = `${import.meta.env.VWED_API_BASE_URL}/api/vwed-map-converter/smap-to-iray`;
|
||||
break;
|
||||
case 'scene-to-smap':
|
||||
apiUrl = import.meta.env.VWED_API_BASE_URL + '/api/vwed-map-converter/scene-to-smap';
|
||||
apiUrl = `${import.meta.env.VWED_API_BASE_URL}/api/vwed-map-converter/scene-to-smap`;
|
||||
break;
|
||||
default:
|
||||
apiUrl = import.meta.env.VWED_API_BASE_URL + '/api/vwed-map-converter/smap-to-scene';
|
||||
apiUrl = `${import.meta.env.VWED_API_BASE_URL}/api/vwed-map-converter/smap-to-scene`;
|
||||
}
|
||||
|
||||
// 如果是iray转换,需要特殊处理二进制响应
|
||||
if (convertType === 'iray') {
|
||||
const response = await http.post<any>(apiUrl, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
@ -115,24 +113,23 @@ const convertMap = async (convertType: string, formData: FormData) => {
|
||||
|
||||
const blob = response?.data as unknown as Blob;
|
||||
if (!blob) {
|
||||
throw new Error('返回的二进制文件为空');
|
||||
throw new Error('返回的压缩包为空');
|
||||
}
|
||||
const disposition = response?.headers?.['content-disposition'] || '';
|
||||
let filename = 'map_package.zip'; // 默认文件名
|
||||
let filename = 'map_package.zip';
|
||||
const filenameMatch = disposition.match(/filename=([^;]+)/i);
|
||||
if (filenameMatch && filenameMatch[1]) {
|
||||
filename = decodeURIComponent(filenameMatch[1].replace(/"/g, ''));
|
||||
}
|
||||
|
||||
return { blob, filename };
|
||||
} else {
|
||||
// 其他情况,保持原有的JSON响应处理
|
||||
return http.post<any>(apiUrl, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
}
|
||||
|
||||
return http.post<any>(apiUrl, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
};
|
||||
// Modal visibility
|
||||
|
||||
const visible = ref(false);
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
@ -143,50 +140,95 @@ const showModal = () => {
|
||||
};
|
||||
defineExpose({ showModal });
|
||||
|
||||
// Component state
|
||||
const loading = ref(false);
|
||||
const convertType = ref('scene');
|
||||
const convertType = ref<ConversionType>('scene');
|
||||
const file1 = ref<File | null>(null);
|
||||
const file2 = ref<File | null>(null);
|
||||
const fileList1 = ref([]);
|
||||
const fileList2 = ref([]);
|
||||
const fileList1 = ref<UploadChangeParam['fileList']>([]);
|
||||
const fileList2 = ref<UploadChangeParam['fileList']>([]);
|
||||
const result = ref('');
|
||||
const downloadUrl = ref('');
|
||||
const downloadFilename = ref('');
|
||||
|
||||
const irayParams = reactive({
|
||||
const defaultIrayParams = {
|
||||
mapWidth: 94744,
|
||||
mapHeight: 79960,
|
||||
xAttrMin: -46762,
|
||||
yAttrMin: -63362,
|
||||
});
|
||||
};
|
||||
const irayParams = reactive({ ...defaultIrayParams });
|
||||
|
||||
// UI labels based on convertType
|
||||
const fileInputLabel = computed(() => {
|
||||
if (convertType.value === 'scene-to-smap') {
|
||||
return '选择 .scene 文件 (必选)';
|
||||
return '上传 Scene 文件(必选)';
|
||||
}
|
||||
return '选择 .smap 文件 (必选)';
|
||||
return '上传 SMAP 文件(必选)';
|
||||
});
|
||||
|
||||
const sceneInputLabel = computed(() => {
|
||||
if (convertType.value === 'iray') {
|
||||
return '选择 .scene 文件 (必选)';
|
||||
return '上传 Scene 文件(必选)';
|
||||
}
|
||||
if (convertType.value === 'scene-to-smap') {
|
||||
return '选择 .smap 文件 (必选)';
|
||||
return '上传 SMAP 文件(必选)';
|
||||
}
|
||||
return '选择 .scene 文件 (可选)';
|
||||
return '上传 Scene 文件(可选)';
|
||||
});
|
||||
|
||||
// File handling
|
||||
const file1Accept = computed(() => (convertType.value === 'scene-to-smap' ? '.scene' : '.smap'));
|
||||
const file2Accept = computed(() => (convertType.value === 'scene-to-smap' ? '.smap' : '.scene'));
|
||||
|
||||
const file1ErrorMessage = computed(() =>
|
||||
convertType.value === 'scene-to-smap' ? '请上传 .scene 格式的文件' : '请上传 .smap 格式的文件',
|
||||
);
|
||||
const file2ErrorMessage = computed(() =>
|
||||
convertType.value === 'scene-to-smap' ? '请上传 .smap 格式的文件' : '请上传 .scene 格式的文件',
|
||||
);
|
||||
|
||||
const parseAccept = (accept: string) =>
|
||||
accept
|
||||
.split(',')
|
||||
.map((item) => item.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
|
||||
const getFileExtension = (fileName: string) => {
|
||||
const dotIndex = fileName.lastIndexOf('.');
|
||||
return dotIndex >= 0 ? fileName.slice(dotIndex).toLowerCase() : '';
|
||||
};
|
||||
|
||||
const isExtensionAllowed = (file: File, accept: string) => {
|
||||
if (!accept) return true;
|
||||
const allowed = parseAccept(accept);
|
||||
if (!allowed.length) return true;
|
||||
const ext = getFileExtension(file.name || '');
|
||||
return allowed.includes(ext);
|
||||
};
|
||||
|
||||
const updateFileSelection = (
|
||||
info: UploadChangeParam,
|
||||
fileRef: Ref<File | null>,
|
||||
listRef: Ref<UploadChangeParam['fileList']>,
|
||||
accept: string,
|
||||
errorMessage: string,
|
||||
) => {
|
||||
const rawFile = (info.file?.originFileObj as File | undefined) ?? null;
|
||||
|
||||
if (rawFile && !isExtensionAllowed(rawFile, accept)) {
|
||||
message.error(errorMessage);
|
||||
fileRef.value = null;
|
||||
listRef.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
fileRef.value = rawFile;
|
||||
listRef.value = info.fileList as UploadChangeParam['fileList'];
|
||||
};
|
||||
|
||||
const handleFile1Change = (info: UploadChangeParam) => {
|
||||
file1.value = (info.file as unknown as File) || null;
|
||||
fileList1.value = info.fileList as never[];
|
||||
updateFileSelection(info, file1, fileList1, file1Accept.value, file1ErrorMessage.value);
|
||||
};
|
||||
const handleFile2Change = (info: UploadChangeParam) => {
|
||||
file2.value = (info.file as unknown as File) || null;
|
||||
fileList2.value = info.fileList as never[];
|
||||
updateFileSelection(info, file2, fileList2, file2Accept.value, file2ErrorMessage.value);
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
@ -197,8 +239,10 @@ const resetState = () => {
|
||||
fileList1.value = [];
|
||||
fileList2.value = [];
|
||||
result.value = '';
|
||||
revokeDownloadUrl();
|
||||
downloadUrl.value = '';
|
||||
downloadFilename.value = '';
|
||||
Object.assign(irayParams, defaultIrayParams);
|
||||
};
|
||||
|
||||
watch(convertType, () => {
|
||||
@ -228,26 +272,26 @@ const handleDownload = () => {
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// Conversion logic
|
||||
const handleConvert = async () => {
|
||||
// Validation
|
||||
if (!file1.value) {
|
||||
message.error('请选择第一个文件');
|
||||
message.error('请先上传主文件');
|
||||
return;
|
||||
}
|
||||
if ((convertType.value === 'iray' || convertType.value === 'scene-to-smap') && !file2.value) {
|
||||
message.error('请选择第二个文件');
|
||||
message.error('请上传辅助文件');
|
||||
return;
|
||||
}
|
||||
if (convertType.value === 'iray') {
|
||||
if (!irayParams.mapWidth || !irayParams.mapHeight || !irayParams.xAttrMin || !irayParams.yAttrMin) {
|
||||
message.error('IRAY转换需要填写所有地图参数');
|
||||
const { mapWidth, mapHeight, xAttrMin, yAttrMin } = irayParams;
|
||||
if (!mapWidth || !mapHeight || xAttrMin === undefined || yAttrMin === undefined) {
|
||||
message.error('IRAY 转换需要填写完整的地图参数');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke previous URL before creating a new one
|
||||
revokeDownloadUrl();
|
||||
downloadUrl.value = '';
|
||||
downloadFilename.value = '';
|
||||
|
||||
const formData = new FormData();
|
||||
if (convertType.value === 'scene-to-smap') {
|
||||
@ -267,52 +311,55 @@ const handleConvert = async () => {
|
||||
|
||||
loading.value = true;
|
||||
result.value = '正在上传并转换...';
|
||||
downloadUrl.value = '';
|
||||
|
||||
try {
|
||||
// SMAP转IRAY时,API会返回二进制流,需要特殊处理
|
||||
if (convertType.value === 'iray') {
|
||||
// convertMap内部已处理responseType,这里直接获取返回的blob和filename
|
||||
const { blob, filename } = await convertMap(convertType.value, formData);
|
||||
console.log('blob', blob);
|
||||
if (blob && blob.size > 0) {
|
||||
revokeDownloadUrl(); // 清理旧的URL
|
||||
revokeDownloadUrl();
|
||||
downloadUrl.value = URL.createObjectURL(blob);
|
||||
downloadFilename.value = filename;
|
||||
result.value = `转换成功!文件 ${filename} 已开始自动下载。`;
|
||||
message.success('IRAY地图包已生成,将自动开始下载。');
|
||||
// 不再显示下载按钮,而是直接触发下载
|
||||
downloadFilename.value = filename || 'map_package.zip';
|
||||
result.value = `转换成功,文件 ${downloadFilename.value} 已开始自动下载。`;
|
||||
message.success('IRAY 地图转换成功,已自动开始下载。');
|
||||
handleDownload();
|
||||
// 隐藏下载按钮,因为已经自动下载
|
||||
setTimeout(() => {
|
||||
revokeDownloadUrl();
|
||||
downloadUrl.value = '';
|
||||
}, 100);
|
||||
} else {
|
||||
throw new Error('返回的二进制文件为空');
|
||||
throw new Error('返回的压缩包为空');
|
||||
}
|
||||
} else {
|
||||
// 其他转换类型保持原有的JSON处理逻辑
|
||||
const data = await convertMap(convertType.value, formData);
|
||||
if (data.code == 200 || data.success) {
|
||||
result.value = '转换成功!';
|
||||
if (data?.code === 200 || data?.success) {
|
||||
result.value = '转换成功。';
|
||||
if (convertType.value === 'scene' && data.data) {
|
||||
const sceneContent = JSON.stringify(data.data, null, 2);
|
||||
const blob = new Blob([sceneContent], { type: 'application/json' });
|
||||
revokeDownloadUrl();
|
||||
downloadUrl.value = URL.createObjectURL(blob);
|
||||
downloadFilename.value = data.data.output_filename || 'converted.scene';
|
||||
message.success('转换成功,已生成 Scene 文件。');
|
||||
} else if (convertType.value === 'scene-to-smap' && data.data) {
|
||||
const smapContent = JSON.stringify(data.data?.data, null, 2);
|
||||
const blob = new Blob([smapContent], { type: 'application/json' });
|
||||
revokeDownloadUrl();
|
||||
downloadUrl.value = URL.createObjectURL(blob);
|
||||
downloadFilename.value = data.data?.output_file ? data.data.output_file.split(/[/\\]/).pop() : 'updated.smap';
|
||||
const outputFile = data.data?.output_file
|
||||
? data.data.output_file.split(/[/\\]/).pop()
|
||||
: undefined;
|
||||
downloadFilename.value = outputFile || 'updated.smap';
|
||||
message.success('转换成功,已生成 SMAP 文件。');
|
||||
}
|
||||
} else {
|
||||
result.value = `转换失败: ${data.message || '未知错误'}`;
|
||||
message.error(`转换失败: ${data.message || '未知错误'}`);
|
||||
const errorText = data?.message || '未知错误';
|
||||
result.value = `转换失败:${errorText}`;
|
||||
message.error(`转换失败:${errorText}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
result.value = '请求失败:' + err;
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
result.value = `处理失败:${errorMessage}`;
|
||||
message.error('转换失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user