导出优化
This commit is contained in:
1 parent
72d8daa283
commit
79ce3165c1
17 files changed
+1559
-1291
No files matched your search
@@ -18,6 +18,11 @@ export interface ConnectorImage {
|
||||
dataBase64: string
|
||||
}
|
||||
|
||||
export interface SpineConnectorProgress {
|
||||
phase: 'images' | 'upload' | 'processing' | 'download'
|
||||
progress: number | null
|
||||
}
|
||||
|
||||
interface ConnectorErrorBody {
|
||||
code?: string
|
||||
message?: string
|
||||
@@ -74,21 +79,26 @@ async function sourceToImage(source: string, folder: string, name: string): Prom
|
||||
}
|
||||
|
||||
/** 收集 Spine JSON 中附件会引用的粒子与拖尾图片。 */
|
||||
export async function collectSpineExportImages(systems: ParticleSystem[]) {
|
||||
export async function collectSpineExportImages(systems: ParticleSystem[], onProgress?: (completed: number, total: number) => void) {
|
||||
const requests: Array<Promise<ConnectorImage | null>> = []
|
||||
let completed = 0
|
||||
const track = (request: Promise<ConnectorImage | null>) => request.finally(() => {
|
||||
completed++
|
||||
onProgress?.(completed, requests.length)
|
||||
})
|
||||
for (const system of systems.filter((item) => item.visible !== false)) {
|
||||
for (const resource of system.config.imageResources) {
|
||||
if (resource.imageMode === 'sequence' && resource.sequenceFrames.length) {
|
||||
for (const frame of resource.sequenceFrames) {
|
||||
requests.push(sourceToImage(frame.previewUrl, resource.textureFolder, frame.name || resource.textureName))
|
||||
requests.push(track(sourceToImage(frame.previewUrl, resource.textureFolder, frame.name || resource.textureName)))
|
||||
}
|
||||
} else {
|
||||
requests.push(sourceToImage(resource.previewUrl, resource.textureFolder, resource.textureName))
|
||||
requests.push(track(sourceToImage(resource.previewUrl, resource.textureFolder, resource.textureName)))
|
||||
}
|
||||
}
|
||||
if (system.config.trail) {
|
||||
for (const resource of system.config.trailResources) {
|
||||
requests.push(sourceToImage(resource.previewUrl, '', resource.textureName))
|
||||
requests.push(track(sourceToImage(resource.previewUrl, '', resource.textureName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,39 +123,53 @@ export async function convertWithSpineConnector(input: {
|
||||
spineVersion: SpineExportVersion
|
||||
skeletonJson: string
|
||||
systems: ParticleSystem[]
|
||||
}) {
|
||||
const images = await collectSpineExportImages(input.systems)
|
||||
let response: Response
|
||||
try {
|
||||
response = await fetch(`${CONNECTOR_URL}/v1/convert`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
projectName: input.projectName,
|
||||
spineVersion: input.spineVersion,
|
||||
skeletonJson: input.skeletonJson,
|
||||
images,
|
||||
}),
|
||||
})
|
||||
} catch {
|
||||
throw new SpineConnectorError('未检测到本地 Spine 连接器,请先启动连接器', 'CONNECTOR_OFFLINE')
|
||||
}
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => ({} as ConnectorErrorBody)) as ConnectorErrorBody
|
||||
throw new SpineConnectorError(body.message || `Spine 连接器返回错误:HTTP ${response.status}`, body.code, body.pairUrl)
|
||||
}
|
||||
const encodedFileName = response.headers.get('X-Spine-File-Name')
|
||||
let fileName = `${cleanPart(input.projectName, 'SpineParticle')}.zip`
|
||||
if (encodedFileName) {
|
||||
try { fileName = decodeURIComponent(encodedFileName) } catch { /* 保留安全的默认文件名 */ }
|
||||
}
|
||||
return {
|
||||
blob: await response.blob(),
|
||||
fileName,
|
||||
spineEditorVersion: response.headers.get('X-Spine-Editor-Version') || input.spineVersion,
|
||||
imageCount: images.length,
|
||||
}
|
||||
}, onProgress?: (progress: SpineConnectorProgress) => void) {
|
||||
const images = await collectSpineExportImages(input.systems, (completed, total) => {
|
||||
onProgress?.({ phase: 'images', progress: total ? completed / total : 1 })
|
||||
})
|
||||
const body = JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
projectName: input.projectName,
|
||||
spineVersion: input.spineVersion,
|
||||
skeletonJson: input.skeletonJson,
|
||||
images,
|
||||
})
|
||||
|
||||
return new Promise<{ blob: Blob; fileName: string; spineEditorVersion: string; imageCount: number }>((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open('POST', `${CONNECTOR_URL}/v1/convert`)
|
||||
request.responseType = 'blob'
|
||||
request.setRequestHeader('Content-Type', 'application/json')
|
||||
request.upload.onprogress = (event) => {
|
||||
onProgress?.({ phase: 'upload', progress: event.lengthComputable && event.total > 0 ? event.loaded / event.total : null })
|
||||
}
|
||||
request.upload.onloadend = () => onProgress?.({ phase: 'processing', progress: null })
|
||||
request.onprogress = (event) => {
|
||||
onProgress?.({ phase: 'download', progress: event.lengthComputable && event.total > 0 ? event.loaded / event.total : null })
|
||||
}
|
||||
request.onerror = () => reject(new SpineConnectorError('未检测到本地 Spine 连接器,请先启动连接器', 'CONNECTOR_OFFLINE'))
|
||||
request.onload = async () => {
|
||||
const responseBlob = request.response as Blob
|
||||
if (request.status < 200 || request.status >= 300) {
|
||||
let errorBody: ConnectorErrorBody = {}
|
||||
try { errorBody = JSON.parse(await responseBlob.text()) as ConnectorErrorBody } catch { /* 使用 HTTP 回退信息 */ }
|
||||
reject(new SpineConnectorError(errorBody.message || `Spine 连接器返回错误:HTTP ${request.status}`, errorBody.code, errorBody.pairUrl))
|
||||
return
|
||||
}
|
||||
const encodedFileName = request.getResponseHeader('X-Spine-File-Name')
|
||||
let fileName = `${cleanPart(input.projectName, 'SpineParticle')}.zip`
|
||||
if (encodedFileName) {
|
||||
try { fileName = decodeURIComponent(encodedFileName) } catch { /* 保留安全的默认文件名 */ }
|
||||
}
|
||||
resolve({
|
||||
blob: responseBlob,
|
||||
fileName,
|
||||
spineEditorVersion: request.getResponseHeader('X-Spine-Editor-Version') || input.spineVersion,
|
||||
imageCount: images.length,
|
||||
})
|
||||
}
|
||||
request.send(body)
|
||||
})
|
||||
}
|
||||
|
||||
export function downloadSpineProject(blob: Blob, fileName: string) {
|
||||
|
||||
Reference in new issue
Block a user