Skip to content

Commit 494b7aa

Browse files
committed
fix: smooth gui sync elapsed timer
1 parent 98a58ef commit 494b7aa

3 files changed

Lines changed: 482 additions & 4 deletions

File tree

quantclass_sync_internal/gui/assets/app.js

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
function deriveSyncStartedAtMs(syncStartedAtMs, backendElapsedSeconds, nowMs) {
2+
if (syncStartedAtMs != null) return syncStartedAtMs;
3+
const elapsedSeconds = Math.max(0, Number(backendElapsedSeconds) || 0);
4+
if (elapsedSeconds > 0) return nowMs - elapsedSeconds * 1000;
5+
return nowMs;
6+
}
7+
8+
function computeDisplayedElapsedSeconds(syncStartedAtMs, backendElapsedSeconds, nowMs) {
9+
const elapsedSeconds = Math.max(0, Number(backendElapsedSeconds) || 0);
10+
if (syncStartedAtMs == null) return elapsedSeconds;
11+
return Math.max(elapsedSeconds, Math.max(0, (nowMs - syncStartedAtMs) / 1000));
12+
}
13+
114
// Alpine.js 数据组件
215
// 等待 alpine:init 事件注册组件,再等待 pywebviewready 后初始化数据
316
document.addEventListener('alpine:init', () => {
@@ -55,6 +68,9 @@ document.addEventListener('alpine:init', () => {
5568
completed: 0, // 已完成产品数
5669
total: 0, // 总产品数
5770
elapsedSeconds: 0, // 已用秒数
71+
displayElapsedSeconds: 0, // 顶部展示用耗时,可在前端平滑递增
72+
pausedDisplayElapsedSeconds: null, // confirm_needed 时冻结的展示耗时
73+
syncStartedAtMs: null, // 前端本地记录的同步起点,用于平滑显示总耗时
5874
errorMessage: '', // 错误信息(error 状态时)
5975
runSummary: null, // 完成后的摘要对象
6076
pollTimer: null, // setTimeout 句柄
@@ -178,6 +194,9 @@ document.addEventListener('alpine:init', () => {
178194
this.total = 0;
179195
this.currentProduct = '';
180196
this.elapsedSeconds = 0;
197+
this.displayElapsedSeconds = 0;
198+
this.pausedDisplayElapsedSeconds = null;
199+
this.syncStartedAtMs = null;
181200
this.errorMessage = '';
182201
this.runSummary = null;
183202
this.syncProducts = [];
@@ -189,16 +208,19 @@ document.addEventListener('alpine:init', () => {
189208
try {
190209
const result = await window.pywebview.api.start_sync();
191210
if (result.started) {
211+
this.syncStartedAtMs = Date.now();
192212
this.startPolling();
193213
} else {
194214
// Python 端拒绝启动(如已有任务在跑)
195215
this.errorMessage = result.message || '无法启动同步';
196216
this.syncStatus = 'error';
217+
this.syncStartedAtMs = null;
197218
}
198219
} catch (e) {
199220
console.error('startSync failed:', e);
200221
this.errorMessage = String(e);
201222
this.syncStatus = 'error';
223+
this.syncStartedAtMs = null;
202224
}
203225
},
204226

@@ -211,6 +233,9 @@ document.addEventListener('alpine:init', () => {
211233
this.total = 0;
212234
this.currentProduct = '';
213235
this.elapsedSeconds = 0;
236+
this.displayElapsedSeconds = 0;
237+
this.pausedDisplayElapsedSeconds = null;
238+
this.syncStartedAtMs = null;
214239
this.errorMessage = '';
215240
this.runSummary = null;
216241
this.syncProducts = [];
@@ -223,15 +248,18 @@ document.addEventListener('alpine:init', () => {
223248
// true 表示仅重试失败产品
224249
const result = await window.pywebview.api.start_sync(true);
225250
if (result.started) {
251+
this.syncStartedAtMs = Date.now();
226252
this.startPolling();
227253
} else {
228254
this.errorMessage = result.message || '无法启动同步';
229255
this.syncStatus = 'error';
256+
this.syncStartedAtMs = null;
230257
}
231258
} catch (e) {
232259
console.error('retryFailed failed:', e);
233260
this.errorMessage = String(e);
234261
this.syncStatus = 'error';
262+
this.syncStartedAtMs = null;
235263
}
236264
},
237265

@@ -252,7 +280,7 @@ document.addEventListener('alpine:init', () => {
252280
this.currentProduct = p.current_product || '';
253281
this.completed = p.completed || 0;
254282
this.total = p.total || 0;
255-
this.elapsedSeconds = p.elapsed_seconds || 0;
283+
this.syncElapsedFromProgress(p);
256284

257285
// 更新产品列表和全部产品名
258286
if (p.products && Array.isArray(p.products)) {
@@ -266,7 +294,8 @@ document.addEventListener('alpine:init', () => {
266294
if (p.status === 'confirm_needed' && p.estimate) {
267295
this.estimateData = p.estimate;
268296
this.postprocessing = false;
269-
// 不切换 syncStatus,继续轮询等待用户点击确认/取消
297+
this.pollTimer = null;
298+
return; // 等待用户确认,不再继续轮询
270299
} else if (p.status === 'postprocessing') {
271300
this.postprocessing = true;
272301
this.postprocessDetail = p.postprocess_detail || '';
@@ -275,6 +304,7 @@ document.addEventListener('alpine:init', () => {
275304
this.postprocessing = false;
276305
this.postprocessDetail = '';
277306
this.estimateData = null;
307+
this.syncStartedAtMs = null;
278308
this.runSummary = p.run_summary;
279309
this.historyLoaded = false; // 有新运行,下次切历史页时刷新
280310
this.checkUpdateResult = null; // 同步后清除检查更新结果
@@ -285,12 +315,21 @@ document.addEventListener('alpine:init', () => {
285315
this.postprocessing = false;
286316
this.postprocessDetail = '';
287317
this.estimateData = null;
318+
this.syncStartedAtMs = null;
288319
this.errorMessage = p.error_message || '同步失败';
289320
this.runSummary = p.run_summary; // 部分失败时也携带摘要
290321
this.historyLoaded = false;
291322
this.checkUpdateResult = null;
292323
this.pollTimer = null;
293324
return; // 终态,不再调度下次轮询
325+
} else if (p.status === 'idle') {
326+
this.syncStatus = 'idle';
327+
this.postprocessing = false;
328+
this.postprocessDetail = '';
329+
this.estimateData = null;
330+
this.syncStartedAtMs = null;
331+
this.pollTimer = null;
332+
return; // 终态,不再调度下次轮询
294333
}
295334
} catch (e) {
296335
console.error('poll failed:', e);
@@ -318,11 +357,15 @@ document.addEventListener('alpine:init', () => {
318357
this.completed = 0;
319358
this.total = 0;
320359
this.elapsedSeconds = 0;
360+
this.displayElapsedSeconds = 0;
361+
this.pausedDisplayElapsedSeconds = null;
362+
this.syncStartedAtMs = null;
321363
this.errorMessage = '';
322364
this.runSummary = null;
323365
this.syncProducts = [];
324366
this.allProducts = [];
325367
this.estimateData = null;
368+
this.pausedDisplayElapsedSeconds = null;
326369
},
327370

328371
// ===== API 调用量确认 =====
@@ -331,10 +374,13 @@ document.addEventListener('alpine:init', () => {
331374
async confirmSync() {
332375
try {
333376
await window.pywebview.api.confirm_sync();
377+
this.estimateData = null;
378+
this.pausedDisplayElapsedSeconds = null;
379+
this.syncStartedAtMs = Date.now() - this.displayElapsedSeconds * 1000;
380+
this.startPolling();
334381
} catch (e) {
335382
console.error('confirmSync failed:', e);
336383
}
337-
this.estimateData = null;
338384
},
339385

340386
// 用户点击"取消":通知后台线程取消,await 完成后再切状态(避免请求期间状态已变)
@@ -346,10 +392,46 @@ document.addEventListener('alpine:init', () => {
346392
} finally {
347393
this.estimateData = null;
348394
this.syncStatus = 'idle';
395+
this.pausedDisplayElapsedSeconds = null;
396+
this.syncStartedAtMs = null;
349397
this.stopPolling();
350398
}
351399
},
352400

401+
syncElapsedFromProgress(progress) {
402+
const backendElapsedSeconds = progress && progress.elapsed_seconds != null
403+
? progress.elapsed_seconds
404+
: 0;
405+
const status = progress && progress.status ? progress.status : '';
406+
this.elapsedSeconds = backendElapsedSeconds;
407+
if (status === 'confirm_needed') {
408+
if (this.pausedDisplayElapsedSeconds == null) {
409+
this.pausedDisplayElapsedSeconds = this.displayElapsedSeconds > 0
410+
? this.displayElapsedSeconds
411+
: backendElapsedSeconds;
412+
}
413+
this.displayElapsedSeconds = this.pausedDisplayElapsedSeconds;
414+
this.syncStartedAtMs = null;
415+
return;
416+
}
417+
this.pausedDisplayElapsedSeconds = null;
418+
if (status === 'done' || status === 'error' || status === 'idle') {
419+
this.displayElapsedSeconds = backendElapsedSeconds;
420+
return;
421+
}
422+
const nowMs = Date.now();
423+
this.syncStartedAtMs = deriveSyncStartedAtMs(
424+
this.syncStartedAtMs,
425+
backendElapsedSeconds,
426+
nowMs,
427+
);
428+
this.displayElapsedSeconds = computeDisplayedElapsedSeconds(
429+
this.syncStartedAtMs,
430+
backendElapsedSeconds,
431+
nowMs,
432+
);
433+
},
434+
353435
// ===== 格式化工具函数 =====
354436

355437
// 进度百分比,total=0 时返回 0 避免除零

quantclass_sync_internal/gui/assets/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ <h2 class="setup-title">QuantClass Sync 初始设置</h2>
423423
</div>
424424
<!-- 耗时/预估 -->
425425
<div class="qs-meta-row">
426-
<span x-text="'已用时: ' + formatDuration(elapsedSeconds)"></span>
426+
<span x-text="'已用时: ' + formatDuration(displayElapsedSeconds)"></span>
427427
<span x-text="'预估剩余: ' + estimatedRemaining()"></span>
428428
</div>
429429
<!-- 产品列表 -->

0 commit comments

Comments
 (0)