feat: 更新场景转换功能,支持在导出时传递 IRAY 参数,优化导出逻辑和用户交互
This commit is contained in:
parent
b810c7560f
commit
b665cd2032
163
src/components/modal/ExportConverterModal.vue
Normal file
163
src/components/modal/ExportConverterModal.vue
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||||
|
import type { UploadProps } from 'ant-design-vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { computed, reactive, type Ref, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
// --- Type Definitions ---
|
||||||
|
interface IrayParams {
|
||||||
|
mapWidth: number | null;
|
||||||
|
mapHeight: number | null;
|
||||||
|
xAttrMin: number | null;
|
||||||
|
yAttrMin: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExportFormat = 'smap' | 'iray';
|
||||||
|
|
||||||
|
export interface ExportConfirmPayload {
|
||||||
|
format: ExportFormat;
|
||||||
|
smapFile: File;
|
||||||
|
irayParams?: IrayParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Props and Emits ---
|
||||||
|
type Props = {
|
||||||
|
visible: boolean;
|
||||||
|
};
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits(['update:visible', 'confirm']);
|
||||||
|
|
||||||
|
// --- Component State ---
|
||||||
|
const format = ref<ExportFormat>('smap');
|
||||||
|
const fileList: Ref<UploadProps['fileList']> = ref([]);
|
||||||
|
const irayFormState = reactive<IrayParams>({
|
||||||
|
mapWidth: 94744,
|
||||||
|
mapHeight: 79960,
|
||||||
|
xAttrMin: -46762,
|
||||||
|
yAttrMin: -63362,
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Computed Properties ---
|
||||||
|
const isOkButtonDisabled = computed(() => {
|
||||||
|
if (!fileList.value || fileList.value.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (format.value === 'iray') {
|
||||||
|
return Object.values(irayFormState).some((v) => v === null || v === undefined);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Logic ---
|
||||||
|
const resetState = () => {
|
||||||
|
format.value = 'smap';
|
||||||
|
fileList.value = [];
|
||||||
|
Object.assign(irayFormState, {
|
||||||
|
mapWidth: 94744,
|
||||||
|
mapHeight: 79960,
|
||||||
|
xAttrMin: -46762,
|
||||||
|
yAttrMin: -63362,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
// Use setTimeout to allow closing animation to finish before reset
|
||||||
|
setTimeout(resetState, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
if (fileList.value && fileList.value.length > 0) {
|
||||||
|
const smapFile = fileList.value[0].originFileObj as File;
|
||||||
|
const payload: ExportConfirmPayload = {
|
||||||
|
format: format.value,
|
||||||
|
smapFile,
|
||||||
|
};
|
||||||
|
if (format.value === 'iray') {
|
||||||
|
payload.irayParams = { ...irayFormState };
|
||||||
|
}
|
||||||
|
emit('confirm', payload);
|
||||||
|
emit('update:visible', false);
|
||||||
|
} else {
|
||||||
|
message.error('请选择一个基础 SMAP 文件');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||||
|
fileList.value = [file];
|
||||||
|
return false; // Prevent automatic upload
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="props.visible"
|
||||||
|
title="导出为其他格式"
|
||||||
|
ok-text="开始导出"
|
||||||
|
cancel-text="取消"
|
||||||
|
:ok-button-props="{ disabled: isOkButtonDisabled }"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="选择导出格式">
|
||||||
|
<a-radio-group v-model:value="format" button-style="solid" style="width: 100%">
|
||||||
|
<a-radio-button value="smap" style="width: 50%">导出为 SMAP (.smap)</a-radio-button>
|
||||||
|
<a-radio-button value="iray" style="width: 50%">导出为 IRAY (.zip)</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="选择基础 SMAP 文件">
|
||||||
|
<a-upload-dragger
|
||||||
|
v-model:fileList="fileList"
|
||||||
|
name="file"
|
||||||
|
:multiple="false"
|
||||||
|
accept=".smap"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
>
|
||||||
|
<p class="ant-upload-drag-icon">
|
||||||
|
<cloud-upload-outlined />
|
||||||
|
</p>
|
||||||
|
<p class="ant-upload-text">点击或拖拽 SMAP 文件到此区域</p>
|
||||||
|
<p class="ant-upload-hint">必须提供一个基础 .smap 文件用于导出。</p>
|
||||||
|
</a-upload-dragger>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<div v-if="format === 'iray'">
|
||||||
|
<a-divider>IRAY 转换参数 (必填)</a-divider>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="地图宽度 (mm)">
|
||||||
|
<a-input-number v-model:value="irayFormState.mapWidth" placeholder="例如: 94744" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="地图高度 (mm)">
|
||||||
|
<a-input-number v-model:value="irayFormState.mapHeight" placeholder="例如: 79960" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="X最小值 (mm)">
|
||||||
|
<a-input-number v-model:value="irayFormState.xAttrMin" placeholder="例如: -46762" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="Y最小值 (mm)">
|
||||||
|
<a-input-number v-model:value="irayFormState.yAttrMin" placeholder="例如: -63362" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
@ -90,7 +90,12 @@ export function useMapConversion() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertSceneToIray = async (sceneJson: string, smapFile: File, filename: string) => {
|
const convertSceneToIray = async (
|
||||||
|
sceneJson: string,
|
||||||
|
smapFile: File,
|
||||||
|
filename: string,
|
||||||
|
irayParams?: { mapWidth: number; mapHeight: number; xAttrMin: number; yAttrMin: number },
|
||||||
|
) => {
|
||||||
isConverting.value = true;
|
isConverting.value = true;
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@ -98,6 +103,14 @@ export function useMapConversion() {
|
|||||||
formData.append('scene_file', sceneBlob, `${filename}.scene`);
|
formData.append('scene_file', sceneBlob, `${filename}.scene`);
|
||||||
formData.append('smap_file', smapFile);
|
formData.append('smap_file', smapFile);
|
||||||
|
|
||||||
|
// Append IRAY parameters if they exist
|
||||||
|
if (irayParams) {
|
||||||
|
formData.append('map_width', String(irayParams.mapWidth));
|
||||||
|
formData.append('map_height', String(irayParams.mapHeight));
|
||||||
|
formData.append('x_attr_min', String(irayParams.xAttrMin));
|
||||||
|
formData.append('y_attr_min', String(irayParams.yAttrMin));
|
||||||
|
}
|
||||||
|
|
||||||
const response = await mapConverterHttp.post(`${API_BASE_URL}/smap-to-iray`, formData, {
|
const response = await mapConverterHttp.post(`${API_BASE_URL}/smap-to-iray`, formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import expandIcon from '../assets/icons/png/expand.png';
|
|||||||
import foldIcon from '../assets/icons/png/fold.png';
|
import foldIcon from '../assets/icons/png/fold.png';
|
||||||
import BatchEditToolbar from '../components/batch-edit-toolbar.vue';
|
import BatchEditToolbar from '../components/batch-edit-toolbar.vue';
|
||||||
import AutoCreateStorageModal from '../components/modal/auto-create-storage-modal.vue';
|
import AutoCreateStorageModal from '../components/modal/auto-create-storage-modal.vue';
|
||||||
|
import ExportConverterModal, { type ExportConfirmPayload } from '../components/modal/ExportConverterModal.vue';
|
||||||
import ImportSmapModal from '../components/modal/ImportSmapModal.vue';
|
import ImportSmapModal from '../components/modal/ImportSmapModal.vue';
|
||||||
import { useMapConversion } from '../hooks/useMapConversion';
|
import { useMapConversion } from '../hooks/useMapConversion';
|
||||||
import { EditorService } from '../services/editor.service';
|
import { EditorService } from '../services/editor.service';
|
||||||
@ -163,13 +164,7 @@ const importScene = async () => {
|
|||||||
|
|
||||||
const importSmapModalVisible = ref(false);
|
const importSmapModalVisible = ref(false);
|
||||||
|
|
||||||
const handleImportSmapConfirm = async ({
|
const handleImportSmapConfirm = async ({ smapFile, keepProperties }: { smapFile: File; keepProperties: boolean }) => {
|
||||||
smapFile,
|
|
||||||
keepProperties,
|
|
||||||
}: {
|
|
||||||
smapFile: File;
|
|
||||||
keepProperties: boolean;
|
|
||||||
}) => {
|
|
||||||
let sceneJson: string | null = null;
|
let sceneJson: string | null = null;
|
||||||
if (keepProperties) {
|
if (keepProperties) {
|
||||||
// Update mode
|
// Update mode
|
||||||
@ -209,6 +204,8 @@ const importBinTask = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Export Logic ---
|
// --- Export Logic ---
|
||||||
|
const exportModalVisible = ref(false);
|
||||||
|
|
||||||
const exportScene = () => {
|
const exportScene = () => {
|
||||||
const json = editor.value?.save();
|
const json = editor.value?.save();
|
||||||
if (!json) return;
|
if (!json) return;
|
||||||
@ -219,37 +216,23 @@ const exportScene = () => {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportSmap = async () => {
|
const handleExportConfirm = async (payload: ExportConfirmPayload) => {
|
||||||
const sceneJson = editor.value?.save();
|
const sceneJson = editor.value?.save();
|
||||||
if (!sceneJson) {
|
if (!sceneJson) {
|
||||||
message.error('无法获取当前场景数据,请确保场景不为空');
|
message.error('无法获取当前场景数据,请确保场景不为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.info('请选择一个基础 SMAP 文件用于导出');
|
if (payload.format === 'smap') {
|
||||||
const smapFile = await selectFile('.smap');
|
|
||||||
if (!smapFile) return;
|
|
||||||
|
|
||||||
const sceneBlob = new Blob([sceneJson], { type: 'application/json' });
|
const sceneBlob = new Blob([sceneJson], { type: 'application/json' });
|
||||||
const sceneFile = new File([sceneBlob], `${title.value || 'current'}.scene`, {
|
const sceneFile = new File([sceneBlob], `${title.value || 'current'}.scene`, {
|
||||||
type: 'application/json',
|
type: 'application/json',
|
||||||
});
|
});
|
||||||
|
await exportSceneToSmap(sceneFile, payload.smapFile, payload.smapFile.name.replace(/\.smap$/i, ''));
|
||||||
await exportSceneToSmap(sceneFile, smapFile, smapFile.name.replace(/\.smap$/i, ''));
|
} else if (payload.format === 'iray') {
|
||||||
};
|
// @ts-ignore
|
||||||
|
await convertSceneToIray(sceneJson, payload.smapFile, title.value || 'unknown', payload.irayParams);
|
||||||
const exportAsIray = async () => {
|
|
||||||
const json = editor.value?.save();
|
|
||||||
if (!json) {
|
|
||||||
message.error('无法获取当前场景数据,请确保场景不为空');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message.info('请选择一个 SMAP 文件用于 IRAY 导出');
|
|
||||||
const smapFile = await selectFile('.smap');
|
|
||||||
if (!smapFile) return;
|
|
||||||
|
|
||||||
await convertSceneToIray(json, smapFile, title.value || 'unknown');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = ref<boolean>(true);
|
const show = ref<boolean>(true);
|
||||||
@ -340,8 +323,7 @@ const handleAutoCreateStorageCancel = () => {
|
|||||||
{{ $t('导出') }}
|
{{ $t('导出') }}
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item key="1" @click="exportSmap">导出为 SMAP (.smap)</a-menu-item>
|
<a-menu-item key="1" @click="exportModalVisible = true">导出为其他格式</a-menu-item>
|
||||||
<a-menu-item key="2" @click="exportAsIray">导出为 IRAY (.zip)</a-menu-item>
|
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown-button>
|
</a-dropdown-button>
|
||||||
@ -396,6 +378,8 @@ const handleAutoCreateStorageCancel = () => {
|
|||||||
|
|
||||||
<ImportSmapModal v-model:visible="importSmapModalVisible" @confirm="handleImportSmapConfirm" />
|
<ImportSmapModal v-model:visible="importSmapModalVisible" @confirm="handleImportSmapConfirm" />
|
||||||
|
|
||||||
|
<ExportConverterModal v-model:visible="exportModalVisible" @confirm="handleExportConfirm" />
|
||||||
|
|
||||||
<AutoCreateStorageModal
|
<AutoCreateStorageModal
|
||||||
v-model:visible="autoCreateStorageVisible"
|
v-model:visible="autoCreateStorageVisible"
|
||||||
:area-name="autoCreateStorageData.areaName"
|
:area-name="autoCreateStorageData.areaName"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user