feat: 更新场景转换功能,支持通过 SMAP 文件更新场景,重命名导出函数并优化用户提示信息

This commit is contained in:
xudan 2025-09-25 16:23:53 +08:00
parent b5a3fd761c
commit 318775a6e0
2 changed files with 85 additions and 27 deletions

View File

@ -7,30 +7,42 @@ import { downloadFile } from '../services/utils';
const vwedApi = '/vwedApi';
const API_BASE_URL = '' + vwedApi + '/api/vwed-map-converter';
// Create a new Axios instance specifically for map conversion APIs
// to avoid conflicts with the global instance's baseURL.
const mapConverterHttp = axios.create();
export function useMapConversion() {
const isConverting = ref(false);
const convertSmapToScene = async (smapFile: File): Promise<string | null> => {
/**
* Imports or updates a Scene using an SMAP file.
* Calls the smap-to-scene API endpoint.
* @param smapFile The .smap file.
* @param sceneFile Optional .scene file for updates. If not provided, a new scene is created.
* @returns The resulting scene JSON string, or null if failed.
*/
const convertSmapToScene = async (smapFile: File, sceneFile?: File): Promise<string | null> => {
isConverting.value = true;
const isUpdate = !!sceneFile;
const actionText = isUpdate ? '更新' : '新建';
try {
const formData = new FormData();
formData.append('smap_file', smapFile);
if (sceneFile) {
formData.append('scene_file', sceneFile);
}
const response = await mapConverterHttp.post<any>(`${API_BASE_URL}/smap-to-scene`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
// Note: axios wraps the response in a `data` object.
if (response.data.code === 200 || response.data.success) {
message.success('SMAP 转换为 Scene 成功!');
message.success(`通过 SMAP ${actionText} Scene 成功!`);
return JSON.stringify(response.data.data, null, 2);
} else {
throw new Error(response.data.message || '转换失败');
throw new Error(response.data.message || `${actionText}失败`);
}
} catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'SMAP 转换为 Scene 失败';
const errorMessage = error.response?.data?.message || error.message || `通过 SMAP ${actionText} Scene 失败`;
message.error(errorMessage);
return null;
} finally {
@ -38,12 +50,20 @@ export function useMapConversion() {
}
};
const convertSceneToSmap = async (sceneJson: string, filename: string) => {
/**
* Exports a Scene to an SMAP file format.
* Requires both a scene and a base smap file.
* Calls the scene-to-smap API endpoint.
* @param sceneFile The .scene file to export.
* @param smapFile The base .smap file to update.
* @param filename The base name for the downloaded file.
*/
const exportSceneToSmap = async (sceneFile: File, smapFile: File, filename: string) => {
isConverting.value = true;
try {
const formData = new FormData();
const sceneBlob = new Blob([sceneJson], { type: 'application/json' });
formData.append('scene_file', sceneBlob, `${filename}.scene`);
formData.append('scene_file', sceneFile);
formData.append('smap_file', smapFile);
const response = await mapConverterHttp.post<any>(`${API_BASE_URL}/scene-to-smap`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
@ -58,12 +78,12 @@ export function useMapConversion() {
: `${filename}.smap`;
downloadFile(url, outputFilename);
URL.revokeObjectURL(url);
message.success('Scene 转换为 SMAP 并下载成功!');
message.success('导出到 SMAP 文件成功!');
} else {
throw new Error(response.data.message || '转换失败');
throw new Error(response.data.message || '导出失败');
}
} catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Scene 转换为 SMAP 失败';
const errorMessage = error.response?.data?.message || error.message || '导出到 SMAP 文件失败';
message.error(errorMessage);
} finally {
isConverting.value = false;
@ -88,7 +108,7 @@ export function useMapConversion() {
}
const disposition = response?.headers?.['content-disposition'] || '';
let downloadFilename = `${filename}.zip`; // Default
let downloadFilename = `${filename}.zip`;
const filenameMatch = disposition.match(/filename=([^;]+)/i);
if (filenameMatch && filenameMatch[1]) {
downloadFilename = decodeURIComponent(filenameMatch[1].replace(/"/g, ''));
@ -109,7 +129,7 @@ export function useMapConversion() {
return {
isConverting,
convertSmapToScene,
convertSceneToSmap,
exportSceneToSmap,
convertSceneToIray,
};
}

View File

@ -22,7 +22,7 @@ type Props = {
const props = defineProps<Props>();
const { t } = useI18n();
const { isConverting, convertSmapToScene, convertSceneToSmap, convertSceneToIray } = useMapConversion();
const { isConverting, convertSmapToScene, exportSceneToSmap, convertSceneToIray } = useMapConversion();
//#region
const readScene = async () => {
@ -152,27 +152,51 @@ const toPush = () => {
});
};
// --- Import Logic ---
// --- Import/Update Logic ---
const importScene = async () => {
const file = await selectFile('.scene');
if (!file || !file.size) return;
if (!file) return;
const json = await decodeTextFile(file);
editor.value?.load(json, editable.value, undefined, true);
};
const importSmap = async () => {
const file = await selectFile('.smap');
if (!file || !file.size) return;
if (!file) return;
const sceneJson = await convertSmapToScene(file);
if (sceneJson) {
editor.value?.load(sceneJson, editable.value, undefined, true);
}
};
const handleUpdateSceneWithSmap = async () => {
// 1. Get current scene content from editor
const currentSceneJson = editor.value?.save();
if (!currentSceneJson) {
message.error('无法获取当前场景数据,请确保场景不为空');
return;
}
// 2. Prompt user to select the .smap file
message.info('请选择用于更新当前场景的 SMAP 文件');
const smapFile = await selectFile('.smap');
if (!smapFile) return;
// 3. Convert current scene json to a File object in memory
const sceneBlob = new Blob([currentSceneJson], { type: 'application/json' });
const sceneFile = new File([sceneBlob], `${title.value || 'current'}.scene`, { type: 'application/json' });
// 4. Call the update function
const newSceneJson = await convertSmapToScene(smapFile, sceneFile);
if (newSceneJson) {
editor.value?.load(newSceneJson, editable.value, undefined, true);
}
};
const importBinTask = async () => {
try {
const file = await selectFile('.xlsx,.xls');
if (!file || !file.size) return;
if (!file) return;
const success = await importBinTaskExcel(props.id, file);
if (success) {
message.success('Bintask导入成功');
@ -195,10 +219,23 @@ const exportScene = () => {
URL.revokeObjectURL(url);
};
const exportAsSmap = async () => {
const json = editor.value?.save();
if (!json) return;
await convertSceneToSmap(json, title.value || 'unknown');
const exportSmap = async () => {
const sceneJson = editor.value?.save();
if (!sceneJson) {
message.error('无法获取当前场景数据,请确保场景不为空');
return;
}
message.info('请选择一个基础 SMAP 文件用于导出');
const smapFile = await selectFile('.smap');
if (!smapFile) return;
const sceneBlob = new Blob([sceneJson], { type: 'application/json' });
const sceneFile = new File([sceneBlob], `${title.value || 'current'}.scene`, {
type: 'application/json',
});
await exportSceneToSmap(sceneFile, smapFile, smapFile.name.replace(/\.smap$/i, ''));
};
const exportAsIray = async () => {
@ -285,8 +322,9 @@ const handleAutoCreateStorageCancel = () => {
{{ $t('导入') }}
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="importSmap">导入 SMAP (.smap)</a-menu-item>
<a-menu-item key="2" @click="importBinTask">导入 Bintask (.xlsx)</a-menu-item>
<a-menu-item key="1" @click="importSmap"> SMAP 新建 (.smap)</a-menu-item>
<a-menu-item key="2" @click="handleUpdateSceneWithSmap">通过 SMAP 更新 Scene</a-menu-item>
<a-menu-item key="3" @click="importBinTask">导入 Bintask (.xlsx)</a-menu-item>
</a-menu>
</template>
</a-dropdown-button>
@ -295,7 +333,7 @@ const handleAutoCreateStorageCancel = () => {
{{ $t('导出') }}
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="exportAsSmap">导出为 SMAP (.smap)</a-menu-item>
<a-menu-item key="1" @click="exportSmap">导出为 SMAP (.smap)</a-menu-item>
<a-menu-item key="2" @click="exportAsIray">导出为 IRAY (.zip)</a-menu-item>
</a-menu>
</template>