@@ -201,6 +201,113 @@ export class Project extends EventEmitter<{
201201 }
202202 }
203203
204+ /**
205+ * 比较两个版本号字符串(格式:x.y.z)
206+ * @param version1 版本1
207+ * @param version2 版本2
208+ * @returns 如果 version1 < version2 返回 -1,如果 version1 > version2 返回 1,如果相等返回 0
209+ */
210+ private compareVersion ( version1 : string , version2 : string ) : number {
211+ const v1Parts = version1 . split ( "." ) . map ( Number ) ;
212+ const v2Parts = version2 . split ( "." ) . map ( Number ) ;
213+ const maxLength = Math . max ( v1Parts . length , v2Parts . length ) ;
214+
215+ for ( let i = 0 ; i < maxLength ; i ++ ) {
216+ const v1Part = v1Parts [ i ] || 0 ;
217+ const v2Part = v2Parts [ i ] || 0 ;
218+ if ( v1Part < v2Part ) return - 1 ;
219+ if ( v1Part > v2Part ) return 1 ;
220+ }
221+ return 0 ;
222+ }
223+
224+ /**
225+ * 检查是否需要升级,如果需要则显示确认对话框
226+ * @param currentVersion 当前文件版本
227+ * @param latestVersion 最新版本
228+ */
229+ private async checkAndConfirmUpgrade ( currentVersion : string , latestVersion : string ) : Promise < void > {
230+ const needsUpgrade = this . compareVersion ( currentVersion , latestVersion ) < 0 ;
231+
232+ if ( ! needsUpgrade ) {
233+ return ;
234+ }
235+
236+ // 显示确认对话框
237+ const response = await Dialog . buttons (
238+ "检测到旧版本项目文件" ,
239+ `当前文件版本为 ${ currentVersion } ,需要升级到 ${ latestVersion } (是prg文件版本,非软件版本)。\n\n升级过程不可逆且可能存在风险,特别是对于大型文件,建议提前备份。是否继续升级?` ,
240+ [
241+ { id : "cancel" , label : "取消" , variant : "ghost" } ,
242+ { id : "upgrade" , label : "确认升级" } ,
243+ ] ,
244+ ) ;
245+
246+ if ( response === "cancel" ) {
247+ // 用户取消升级,抛出错误以便调用者知道操作被取消
248+ throw new Error ( "用户取消了文件升级,文件未打开" ) ;
249+ }
250+
251+ // 添加延迟,确保用户看到提示并给系统时间处理
252+ await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
253+ }
254+
255+ /**
256+ * 解析项目文件(ZIP格式),提取所有数据
257+ * @returns 解析后的数据对象
258+ */
259+ private async parseProjectFile ( ) : Promise < {
260+ serializedStageObjects : any [ ] ;
261+ tags : string [ ] ;
262+ references : { sections : Record < string , string [ ] > ; files : string [ ] } ;
263+ metadata : ProjectMetadata ;
264+ } > {
265+ const fileContent = await this . fs . read ( this . uri ) ;
266+ const reader = new ZipReader ( new Uint8ArrayReader ( fileContent ) ) ;
267+ const entries = await reader . getEntries ( ) ;
268+
269+ let serializedStageObjects : any [ ] = [ ] ;
270+ let tags : string [ ] = [ ] ;
271+ let references : { sections : Record < string , string [ ] > ; files : string [ ] } = { sections : { } , files : [ ] } ;
272+ let metadata : ProjectMetadata = createDefaultMetadata ( "2.0.0" ) ;
273+
274+ for ( const entry of entries ) {
275+ if ( entry . filename === "stage.msgpack" ) {
276+ const stageRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
277+ serializedStageObjects = this . decoder . decode ( stageRawData ) as any [ ] ;
278+ } else if ( entry . filename === "tags.msgpack" ) {
279+ const tagsRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
280+ tags = this . decoder . decode ( tagsRawData ) as string [ ] ;
281+ } else if ( entry . filename === "reference.msgpack" ) {
282+ const referenceRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
283+ references = this . decoder . decode ( referenceRawData ) as { sections : Record < string , string [ ] > ; files : string [ ] } ;
284+ } else if ( entry . filename === "metadata.msgpack" ) {
285+ const metadataRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
286+ const decodedMetadata = this . decoder . decode ( metadataRawData ) as any ;
287+ // 验证并规范化 metadata
288+ if ( isValidMetadata ( decodedMetadata ) ) {
289+ metadata = decodedMetadata ;
290+ } else {
291+ // 如果格式不正确,使用默认值
292+ metadata = createDefaultMetadata ( "2.0.0" ) ;
293+ }
294+ } else if ( entry . filename . startsWith ( "attachments/" ) ) {
295+ const match = entry . filename . trim ( ) . match ( / ^ a t t a c h m e n t s \/ ( [ a - z A - Z 0 - 9 - ] + ) \. ( [ a - z A - Z 0 - 9 ] + ) $ / ) ;
296+ if ( ! match ) {
297+ console . warn ( "[Project] 附件文件名不符合规范: %s" , entry . filename ) ;
298+ continue ;
299+ }
300+ const uuid = match [ 1 ] ;
301+ const ext = match [ 2 ] ;
302+ const type = mime . getType ( ext ) || "application/octet-stream" ;
303+ const attachment = await entry . getData ! ( new BlobWriter ( type ) ) ;
304+ this . attachments . set ( uuid , attachment ) ;
305+ }
306+ }
307+
308+ return { serializedStageObjects, tags, references, metadata } ;
309+ }
310+
204311 /**
205312 * 服务加载完成后再调用
206313 */
@@ -209,55 +316,25 @@ export class Project extends EventEmitter<{
209316 return ;
210317 }
211318 try {
212- const fileContent = await this . fs . read ( this . uri ) ;
213- const reader = new ZipReader ( new Uint8ArrayReader ( fileContent ) ) ;
214- const entries = await reader . getEntries ( ) ;
215- let serializedStageObjects : any [ ] = [ ] ;
216- let tags : string [ ] = [ ] ;
217- let references : { sections : Record < string , string [ ] > ; files : string [ ] } = { sections : { } , files : [ ] } ;
218- let metadata : ProjectMetadata = createDefaultMetadata ( "2.0.0" ) ;
219-
220- for ( const entry of entries ) {
221- if ( entry . filename === "stage.msgpack" ) {
222- const stageRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
223- serializedStageObjects = this . decoder . decode ( stageRawData ) as any [ ] ;
224- } else if ( entry . filename === "tags.msgpack" ) {
225- const tagsRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
226- tags = this . decoder . decode ( tagsRawData ) as string [ ] ;
227- } else if ( entry . filename === "reference.msgpack" ) {
228- const referenceRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
229- references = this . decoder . decode ( referenceRawData ) as { sections : Record < string , string [ ] > ; files : string [ ] } ;
230- } else if ( entry . filename === "metadata.msgpack" ) {
231- const metadataRawData = await entry . getData ! ( new Uint8ArrayWriter ( ) ) ;
232- const decodedMetadata = this . decoder . decode ( metadataRawData ) as any ;
233- // 验证并规范化 metadata
234- if ( isValidMetadata ( decodedMetadata ) ) {
235- metadata = decodedMetadata ;
236- } else {
237- // 如果格式不正确,使用默认值
238- metadata = createDefaultMetadata ( "2.0.0" ) ;
239- }
240- } else if ( entry . filename . startsWith ( "attachments/" ) ) {
241- const match = entry . filename . trim ( ) . match ( / ^ a t t a c h m e n t s \/ ( [ a - z A - Z 0 - 9 - ] + ) \. ( [ a - z A - Z 0 - 9 ] + ) $ / ) ;
242- if ( ! match ) {
243- console . warn ( "[Project] 附件文件名不符合规范: %s" , entry . filename ) ;
244- continue ;
245- }
246- const uuid = match [ 1 ] ;
247- const ext = match [ 2 ] ;
248- const type = mime . getType ( ext ) || "application/octet-stream" ;
249- const attachment = await entry . getData ! ( new BlobWriter ( type ) ) ;
250- this . attachments . set ( uuid , attachment ) ;
251- }
252- }
319+ // 解析项目文件
320+ const { serializedStageObjects, tags, references, metadata } = await this . parseProjectFile ( ) ;
321+
322+ // 检查并确认升级
323+ const currentVersion = metadata ?. version || "2.0.0" ;
324+ const latestVersion = ProjectUpgrader . NLatestVersion ;
325+ await this . checkAndConfirmUpgrade ( currentVersion , latestVersion ) ;
253326
254327 // 升级数据
255- [ serializedStageObjects , metadata ] = ProjectUpgrader . upgradeNAnyToNLatest ( serializedStageObjects , metadata ) ;
328+ const [ upgradedStageObjects , upgradedMetadata ] = ProjectUpgrader . upgradeNAnyToNLatest (
329+ serializedStageObjects ,
330+ metadata ,
331+ ) ;
256332
257- this . stage = deserialize ( serializedStageObjects , this ) ;
333+ // 应用升级后的数据
334+ this . stage = deserialize ( upgradedStageObjects , this ) ;
258335 this . tags = tags ;
259336 this . references = references ;
260- this . metadata = metadata ;
337+ this . metadata = upgradedMetadata ;
261338 } catch ( e ) {
262339 console . warn ( e ) ;
263340 }
0 commit comments