@@ -504,7 +504,41 @@ function sendMessageToMainPage(action, payload = {}) {
504504// 全局变量:跟踪未保存的更改
505505window . hasUnsavedChanges = false ;
506506
507- // 仅当本页确实保存过配置时,才触发主界面重载(避免退出就把主界面模型/位置“复位”)
507+ // 采集当前所有可保存设置的快照(模型选择 + 打光 + 待机动作)
508+ function captureSettingsSnapshot ( ) {
509+ const modelSelect = document . getElementById ( 'model-select' ) ;
510+ const vrmModelSelect = document . getElementById ( 'vrm-model-select' ) ;
511+ return {
512+ modelType : typeof currentModelType !== 'undefined' ? currentModelType : '' ,
513+ live2d : modelSelect ? modelSelect . value : '' ,
514+ live3d : vrmModelSelect ? vrmModelSelect . value : '' ,
515+ // VRM 打光
516+ ambient : document . getElementById ( 'ambient-light-slider' ) ?. value ?? '' ,
517+ mainLight : document . getElementById ( 'main-light-slider' ) ?. value ?? '' ,
518+ exposure : document . getElementById ( 'exposure-slider' ) ?. value ?? '' ,
519+ toneMapping : document . getElementById ( 'tonemapping-select' ) ?. value ?? '' ,
520+ outlineWidth : document . getElementById ( 'vrm-outline-width-slider' ) ?. value ?? '' ,
521+ // MMD 打光
522+ mmdAmbientIntensity : document . getElementById ( 'mmd-ambient-intensity-slider' ) ?. value ?? '' ,
523+ mmdAmbientColor : document . getElementById ( 'mmd-ambient-color-picker' ) ?. value ?? '' ,
524+ mmdDirectionalIntensity : document . getElementById ( 'mmd-directional-intensity-slider' ) ?. value ?? '' ,
525+ mmdDirectionalColor : document . getElementById ( 'mmd-directional-color-picker' ) ?. value ?? '' ,
526+ mmdExposure : document . getElementById ( 'mmd-exposure-slider' ) ?. value ?? '' ,
527+ mmdToneMapping : document . getElementById ( 'mmd-tonemapping-select' ) ?. value ?? '' ,
528+ mmdOutline : String ( document . getElementById ( 'mmd-outline-toggle' ) ?. checked ?? false ) ,
529+ // 待机动作
530+ idleAnimation : document . getElementById ( 'idle-animation-select' ) ?. value ?? '' ,
531+ mmdIdleAnimation : document . getElementById ( 'mmd-idle-animation-select' ) ?. value ?? '' ,
532+ } ;
533+ }
534+
535+ // 比较两个快照是否一致
536+ function snapshotsEqual ( a , b ) {
537+ if ( ! a || ! b ) return false ;
538+ return Object . keys ( a ) . every ( k => String ( a [ k ] ) === String ( b [ k ] ) ) ;
539+ }
540+
541+ // 仅当本页确实保存过配置时,才触发主界面重载(避免退出就把主界面模型/位置”复位”)
508542window . _modelManagerHasSaved = false ;
509543window . _modelManagerLanlanName = new URLSearchParams ( window . location . search ) . get ( 'lanlan_name' ) || '' ;
510544/**
@@ -1169,8 +1203,8 @@ document.addEventListener('DOMContentLoaded', async () => {
11691203 textSpanId : 'mmd-animation-select-text' ,
11701204 iconClass : 'mmd-animation-select-icon' ,
11711205 iconSrc : '/static/icons/motion_select_icon.png?v=1' ,
1172- defaultText : ' 选择VMD动画',
1173- iconAlt : ' 选择VMD动画',
1206+ defaultText : t ( 'live2d.mmdAnimation.selectAnimation' , ' 选择VMD动画') ,
1207+ iconAlt : t ( 'live2d.mmdAnimation.selectAnimation' , ' 选择VMD动画') ,
11741208 shouldSkipOption : ( option ) => {
11751209 return option . value === '' && (
11761210 option . textContent . includes ( '请先加载' ) ||
@@ -3214,7 +3248,7 @@ document.addEventListener('DOMContentLoaded', async () => {
32143248 mmdAnimations = ( data . success && Array . isArray ( data . animations ) ) ? data . animations : [ ] ;
32153249 if ( ! mmdAnimationSelect ) return ;
32163250
3217- mmdAnimationSelect . innerHTML = ' <option value="">选择VMD动画</option>' ;
3251+ mmdAnimationSelect . innerHTML = ` <option value="">${ t ( 'live2d.mmdAnimation.selectAnimation' , ' 选择VMD动画' ) } </option>` ;
32183252 if ( mmdAnimations . length > 0 ) {
32193253 mmdAnimations . forEach ( anim => {
32203254 const animPath = anim . path || anim . url || ( typeof anim === 'string' ? anim : null ) ;
@@ -4080,6 +4114,7 @@ document.addEventListener('DOMContentLoaded', async () => {
40804114 if ( vrmManager && vrmManager . ambientLight ) {
40814115 vrmManager . ambientLight . intensity = value ;
40824116 }
4117+ window . hasUnsavedChanges = true ;
40834118 } ) ;
40844119 }
40854120
@@ -4091,6 +4126,7 @@ document.addEventListener('DOMContentLoaded', async () => {
40914126 if ( vrmManager && vrmManager . mainLight ) {
40924127 vrmManager . mainLight . intensity = value ;
40934128 }
4129+ window . hasUnsavedChanges = true ;
40944130 } ) ;
40954131 }
40964132
@@ -4146,6 +4182,7 @@ document.addEventListener('DOMContentLoaded', async () => {
41464182 if ( vrmManager && vrmManager . renderer ) {
41474183 vrmManager . renderer . toneMappingExposure = value ;
41484184 }
4185+ window . hasUnsavedChanges = true ;
41494186 } ) ;
41504187 }
41514188
@@ -4173,6 +4210,7 @@ document.addEventListener('DOMContentLoaded', async () => {
41734210 if ( exposureValue ) {
41744211 exposureValue . style . opacity = isNoToneMapping ? '0.5' : '1' ;
41754212 }
4213+ window . hasUnsavedChanges = true ;
41764214 } ) ;
41774215 }
41784216
@@ -4201,6 +4239,7 @@ document.addEventListener('DOMContentLoaded', async () => {
42014239 if ( vrmOutlineWidthSlider ) {
42024240 vrmOutlineWidthSlider . addEventListener ( 'input' , ( e ) => {
42034241 applyVrmOutlineWidth ( parseFloat ( e . target . value ) ) ;
4242+ window . hasUnsavedChanges = true ;
42044243 } ) ;
42054244 }
42064245
@@ -4209,6 +4248,7 @@ document.addEventListener('DOMContentLoaded', async () => {
42094248 idleAnimationSelect . addEventListener ( 'change' , async ( e ) => {
42104249 const selectedUrl = e . target . value ;
42114250 if ( ! selectedUrl ) return ;
4251+ window . hasUnsavedChanges = true ;
42124252 // 实时切换待机动作:停止当前动画,播放新的循环动画
42134253 if ( vrmManager && vrmManager . animation && vrmManager . currentModel ) {
42144254 try {
@@ -4304,6 +4344,7 @@ document.addEventListener('DOMContentLoaded', async () => {
43044344 if ( mmdIdleAnimationSelect ) {
43054345 mmdIdleAnimationSelect . addEventListener ( 'change' , async ( e ) => {
43064346 const selectedUrl = e . target . value ;
4347+ window . hasUnsavedChanges = true ;
43074348 // 实时切换MMD待机动作:停止当前动画,播放新的循环动画
43084349 if ( window . mmdManager && window . mmdManager . currentModel ) {
43094350 try {
@@ -4462,6 +4503,7 @@ document.addEventListener('DOMContentLoaded', async () => {
44624503 const valEl = document . getElementById ( valId ) ;
44634504 if ( valEl ) valEl . textContent = fmt ? fmt ( v ) : v ;
44644505 applyMmdSettings ( ) ;
4506+ window . hasUnsavedChanges = true ;
44654507 } ) ;
44664508 }
44674509 } ) ;
@@ -4476,6 +4518,7 @@ document.addEventListener('DOMContentLoaded', async () => {
44764518 const valEl = document . getElementById ( valId ) ;
44774519 if ( valEl ) valEl . textContent = e . target . value ;
44784520 applyMmdSettings ( ) ;
4521+ window . hasUnsavedChanges = true ;
44794522 } ) ;
44804523 }
44814524 } ) ;
@@ -4495,6 +4538,7 @@ document.addEventListener('DOMContentLoaded', async () => {
44954538 if ( mmdExposureValue ) {
44964539 mmdExposureValue . style . opacity = isNoToneMapping ? '0.5' : '1' ;
44974540 }
4541+ window . hasUnsavedChanges = true ;
44984542 } ) ;
44994543 }
45004544
@@ -4504,6 +4548,7 @@ document.addEventListener('DOMContentLoaded', async () => {
45044548 const statusEl = document . getElementById ( 'mmd-outline-status' ) ;
45054549 if ( statusEl ) statusEl . textContent = e . target . checked ? 'ON' : 'OFF' ;
45064550 applyMmdSettings ( ) ;
4551+ window . hasUnsavedChanges = true ;
45074552 } ) ;
45084553 }
45094554
@@ -5379,11 +5424,7 @@ document.addEventListener('DOMContentLoaded', async () => {
53795424 if ( modelSuccess ) {
53805425 showStatus ( t ( 'live2d.settingsSaved' , '模型设置保存成功!' ) , 2000 ) ;
53815426 window . hasUnsavedChanges = false ;
5382- window . _savedModelSnapshot = {
5383- modelType : currentModelType ,
5384- live2d : modelSelect ? modelSelect . value : '' ,
5385- live3d : vrmModelSelect ? vrmModelSelect . value : '' ,
5386- } ;
5427+ window . _savedModelSnapshot = captureSettingsSnapshot ( ) ;
53875428 window . _modelManagerHasSaved = true ;
53885429 } else {
53895430 showStatus ( t ( 'live2d.saveFailedGeneral' , '保存失败!' ) , 2000 ) ;
@@ -5393,11 +5434,7 @@ document.addEventListener('DOMContentLoaded', async () => {
53935434 if ( positionSuccess && modelSuccess ) {
53945435 showStatus ( t ( 'live2d.settingsSaved' , '位置和模型设置保存成功!' ) , 2000 ) ;
53955436 window . hasUnsavedChanges = false ; // 保存成功后重置标志
5396- window . _savedModelSnapshot = {
5397- modelType : currentModelType ,
5398- live2d : modelSelect ? modelSelect . value : '' ,
5399- live3d : vrmModelSelect ? vrmModelSelect . value : '' ,
5400- } ;
5437+ window . _savedModelSnapshot = captureSettingsSnapshot ( ) ;
54015438 window . _modelManagerHasSaved = true ;
54025439 // 不在保存时立即通知主页,而是在返回主页时通知
54035440 // sendMessageToMainPage('reload_model');
@@ -5407,6 +5444,7 @@ document.addEventListener('DOMContentLoaded', async () => {
54075444 window . _modelManagerHasSaved = true ;
54085445 } else if ( modelSuccess ) {
54095446 showStatus ( t ( 'live2d.modelSavedPositionFailed' , '模型设置保存成功,位置保存失败!' ) , 2000 ) ;
5447+ window . _savedModelSnapshot = captureSettingsSnapshot ( ) ;
54105448 window . _modelManagerHasSaved = true ;
54115449 // 不在保存时立即通知主页,而是在返回主页时通知
54125450 // sendMessageToMainPage('reload_model');
@@ -5467,14 +5505,9 @@ document.addEventListener('DOMContentLoaded', async () => {
54675505 _earlyBackBtn . removeEventListener ( 'click' , _earlyBackHandler ) ;
54685506 }
54695507 backToMainBtn . addEventListener ( 'click' , async ( ) => {
5470- // 检查是否有未保存的更改:先比对当前模型选择和已保存快照,一致则视为无更改
5508+ // 退出前:比对当前设置和已保存快照,完全一致则视为无更改
54715509 if ( window . hasUnsavedChanges && window . _savedModelSnapshot ) {
5472- const snap = window . _savedModelSnapshot ;
5473- // 只比较当前激活分支的模型选择,隐藏分支的变化不触发未保存提示
5474- const branchMatch = currentModelType === 'live2d'
5475- ? ( modelSelect ? modelSelect . value : '' ) === snap . live2d
5476- : ( vrmModelSelect ? vrmModelSelect . value : '' ) === snap . live3d ;
5477- if ( currentModelType === snap . modelType && branchMatch ) {
5510+ if ( snapshotsEqual ( window . _savedModelSnapshot , captureSettingsSnapshot ( ) ) ) {
54785511 window . hasUnsavedChanges = false ;
54795512 }
54805513 }
@@ -7144,12 +7177,10 @@ document.addEventListener('DOMContentLoaded', async () => {
71447177 }
71457178 }
71467179
7147- // 记录初始化完成时各 select 的已保存值,用于退出前判断模型是否真的被改过
7148- window . _savedModelSnapshot = {
7149- modelType : currentModelType ,
7150- live2d : modelSelect ? modelSelect . value : '' ,
7151- live3d : vrmModelSelect ? vrmModelSelect . value : '' ,
7152- } ;
7180+ // 等待异步设置(打光、待机动作等)加载完成后记录快照
7181+ setTimeout ( ( ) => {
7182+ window . _savedModelSnapshot = captureSettingsSnapshot ( ) ;
7183+ } , 500 ) ;
71537184 } catch ( _fatalError ) {
71547185 console . error ( '[模型管理] DOMContentLoaded 致命错误:' , _fatalError ) ;
71557186 const _s = document . getElementById ( 'status-text' ) ;
0 commit comments