feat(文件选择器): 添加重试和返回功能并优化同步状态显示

- 在文件选择器面板中添加重试失败任务和返回功能
- 为配置面板添加同步重试按钮和返回按钮
- 优化同步状态显示逻辑,仅在必要时显示
- 移除调试日志并添加同步支持状态检查
- 更新多语言翻译添加"返回"和"重新同步"文本
这个提交包含在:
Zain 2025-12-29 15:45:07 +08:00
父节点 fc682a7a09
当前提交 b245c81a42
共有 9 个文件被更改,包括 153 次插入 和 27 次删除

查看文件

@ -268,11 +268,6 @@ export class QuarkAdapter extends BasePlatformAdapter {
return { success: true, method: 'none' }; return { success: true, method: 'none' };
} }
logger.info('[DIAG-SYNC-QUARK] syncAfterRename start', {
renameCount: renameInfoById.size,
hasOldName: renameByOldName.size,
});
// Always patch visible DOM for immediate feedback // Always patch visible DOM for immediate feedback
const patchedById = this.applyRenameMappingToDomById(renameInfoById); const patchedById = this.applyRenameMappingToDomById(renameInfoById);
const patchedByOldName = this.applyRenameMappingToDomByOldName(renameByOldName); const patchedByOldName = this.applyRenameMappingToDomByOldName(renameByOldName);
@ -283,17 +278,6 @@ export class QuarkAdapter extends BasePlatformAdapter {
// Best effort: trigger Quark's own re-render so it can rebuild native filename DOM (icon/wrap/etc.) // Best effort: trigger Quark's own re-render so it can rebuild native filename DOM (icon/wrap/etc.)
const rerender = await this.tryTriggerNativeFileListRerender(renameInfoById); const rerender = await this.tryTriggerNativeFileListRerender(renameInfoById);
logger.info('[DIAG-SYNC-QUARK] syncAfterRename patch result', {
rerender,
patchedById,
patchedByOldName,
patchedCount,
dom: {
hasDataFileId: document.querySelectorAll('[data-file-id]').length,
hasFileNameClass: document.querySelectorAll('.file-name').length,
},
});
if (rerender.triggered) { if (rerender.triggered) {
return { success: true, method: 'ui-refresh', message: rerender.message }; return { success: true, method: 'ui-refresh', message: rerender.message };
} }

查看文件

@ -76,6 +76,12 @@ export class ConfigPanel extends LitElement {
@property({ type: String }) @property({ type: String })
syncMessage: string | null = null; syncMessage: string | null = null;
/**
* Whether sync is supported by the current adapter (optional)
*/
@property({ type: Boolean })
syncSupported = false;
/** /**
* Current selected rule type * Current selected rule type
*/ */
@ -235,6 +241,7 @@ export class ConfigPanel extends LitElement {
: this.syncStatus === 'failed' : this.syncStatus === 'failed'
? I18nService.t('sync_failed') ? I18nService.t('sync_failed')
: ''; : '';
const showSyncStatus = Boolean(syncText) || Boolean(this.syncMessage);
return html` return html`
<div class="execution-view"> <div class="execution-view">
@ -270,10 +277,17 @@ export class ConfigPanel extends LitElement {
</div> </div>
</div> </div>
${this.finished ${this.finished && showSyncStatus
? html` ? html`
<div class="sync-status ${this.syncStatus}"> <div class="sync-status ${this.syncStatus}">
${syncText}${this.syncMessage ? `:${this.syncMessage}` : ''} ${syncText}${this.syncMessage ? `:${this.syncMessage}` : ''}
${this.syncSupported && this.syncStatus === 'failed'
? html`
<button class="sync-retry" @click=${this.handleSync}>
${I18nService.t('sync_retry')}
</button>
`
: ''}
</div> </div>
` `
: ''} : ''}
@ -295,13 +309,15 @@ export class ConfigPanel extends LitElement {
`; `;
} }
const failed = this.progress?.failed ?? 0;
return html` return html`
<div class="execution-actions"> <div class="execution-actions">
<button class="button button-primary" ?disabled=${this.syncStatus === 'syncing'} @click=${this.handleSync}> <button class="button button-primary" ?disabled=${failed <= 0} @click=${this.handleRetry}>
${I18nService.t('sync_list')} ${I18nService.t('retry')}
</button> </button>
<button class="button button-default" @click=${this.handleClose}> <button class="button button-default" @click=${this.handleBack}>
${I18nService.t('close')} ${I18nService.t('back')}
</button> </button>
</div> </div>
`; `;
@ -335,9 +351,18 @@ export class ConfigPanel extends LitElement {
); );
} }
private handleClose(): void { private handleRetry(): void {
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('close', { new CustomEvent('retry', {
bubbles: true,
composed: true,
})
);
}
private handleBack(): void {
this.dispatchEvent(
new CustomEvent('back', {
bubbles: true, bubbles: true,
composed: true, composed: true,
}) })
@ -749,6 +774,20 @@ export class ConfigPanel extends LitElement {
border-color: #91caff; border-color: #91caff;
} }
.sync-retry {
margin-left: 8px;
padding: 0;
border: none;
background: transparent;
color: #1677ff;
cursor: pointer;
font-size: 12px;
}
.sync-retry:hover {
text-decoration: underline;
}
.execution-actions { .execution-actions {
display: flex; display: flex;
gap: 10px; gap: 10px;

查看文件

@ -592,7 +592,6 @@ export class FileSelectorPanel extends LitElement {
const result = await this.adapter.syncAfterRename(renames); const result = await this.adapter.syncAfterRename(renames);
this.syncStatus = result.success ? 'success' : 'failed'; this.syncStatus = result.success ? 'success' : 'failed';
this.syncMessage = result.success ? null : result.message || null; this.syncMessage = result.success ? null : result.message || null;
logger.info('[DIAG-SYNC] syncAfterRename result', result);
} catch (error) { } catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error)); const errorObj = error instanceof Error ? error : new Error(String(error));
this.syncStatus = 'failed'; this.syncStatus = 'failed';
@ -641,6 +640,95 @@ export class FileSelectorPanel extends LitElement {
void this.syncAfterRename(); void this.syncAfterRename();
} }
private async handleRetryFailed(): Promise<void> {
if (this.executing || !this.executionFinished) {
return;
}
const failedIds = new Set(this.executionItems.filter((item) => item.done === false).map((item) => item.file.id));
if (failedIds.size === 0) {
return;
}
try {
this.executing = true;
this.executorState = ExecutorState.RUNNING;
this.syncStatus = 'idle';
this.syncMessage = null;
// Reset only failed items back to pending
this.executionItems = this.executionItems.map((item) => {
if (!failedIds.has(item.file.id)) return item;
return { ...item, done: undefined, error: undefined };
});
const tasks = this.executionItems
.map((item, index) => ({ item, index }))
.filter(({ item }) => failedIds.has(item.file.id))
.map(({ item, index }) => ({
file: item.file,
newName: item.newName,
index,
}));
const retryFiles = tasks.map((task) => task.file);
if (retryFiles.length === 0) {
return;
}
this.progress = {
completed: 0,
total: retryFiles.length,
currentFile: '',
success: 0,
failed: 0,
};
const executor = new BatchExecutor(retryFiles, this.ruleConfig, this.adapter, {
requestInterval: this.adapter.getConfig().requestInterval,
tasks,
onProgress: (progress) => {
this.handleProgress(progress);
},
});
this.executor = executor;
const results = await executor.execute();
this.executionResults = results;
this.executorState = executor.getState();
this.applyExecutionResults(results);
this.updateSummaryProgress();
void this.syncAfterRename();
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
this.error = errorObj.message;
logger.error('[FileSelectorPanel] Retry failed:', errorObj);
} finally {
this.executing = false;
}
}
private handleBack(): void {
this.resetExecutionState();
}
private updateSummaryProgress(): void {
if (!this.executionItems.length) return;
const success = this.executionItems.filter((item) => item.done === true).length;
const failed = this.executionItems.filter((item) => item.done === false).length;
const completed = success + failed;
this.progress = {
completed,
total: this.executionItems.length,
currentFile: '',
success,
failed,
};
}
private resetExecutionState(): void { private resetExecutionState(): void {
this.executor = null; this.executor = null;
this.executorState = ExecutorState.IDLE; this.executorState = ExecutorState.IDLE;
@ -712,12 +800,14 @@ export class FileSelectorPanel extends LitElement {
.paused=${this.executorState === ExecutorState.PAUSED} .paused=${this.executorState === ExecutorState.PAUSED}
.syncStatus=${this.syncStatus} .syncStatus=${this.syncStatus}
.syncMessage=${this.syncMessage} .syncMessage=${this.syncMessage}
.syncSupported=${!!this.adapter.syncAfterRename}
@config-change=${this.handleConfigChange} @config-change=${this.handleConfigChange}
@execute=${this.handleExecute} @execute=${this.handleExecute}
@pause=${this.handlePause} @pause=${this.handlePause}
@cancel=${this.handleCancel} @cancel=${this.handleCancel}
@sync=${this.handleSync} @sync=${this.handleSync}
@close=${this.handleClose} @retry=${this.handleRetryFailed}
@back=${this.handleBack}
></config-panel> ></config-panel>
<file-list-panel <file-list-panel

查看文件

@ -12,6 +12,8 @@ export interface BatchExecutorOptions {
requestInterval: number; requestInterval: number;
/** 是否跳过无需变更的任务(默认不跳过,保持与平台行为一致) */ /** 是否跳过无需变更的任务(默认不跳过,保持与平台行为一致) */
skipUnchanged?: boolean; skipUnchanged?: boolean;
/** 自定义任务列表(可选,用于重试等场景,优先级高于 rule 生成的任务) */
tasks?: Task[];
/** 进度回调 */ /** 进度回调 */
onProgress?: (progress: ProgressEvent) => void; onProgress?: (progress: ProgressEvent) => void;
/** 完成回调 */ /** 完成回调 */
@ -105,7 +107,10 @@ export class BatchExecutor {
try { try {
// 准备所有重命名任务 // 准备所有重命名任务
const tasks = this.prepareTasks(); const preparedTasks = this.options.tasks ?? this.prepareTasks();
const tasks = this.options.skipUnchanged
? preparedTasks.filter((task) => task.newName !== task.file.name)
: preparedTasks;
this.tasks = tasks; this.tasks = tasks;
if (tasks.length === 0) { if (tasks.length === 0) {

查看文件

@ -11,6 +11,7 @@
"loading_files": "Loading files...", "loading_files": "Loading files...",
"error_title": "Error", "error_title": "Error",
"retry": "Retry", "retry": "Retry",
"back": "Back",
"close": "Close", "close": "Close",
"search_placeholder": "Search files", "search_placeholder": "Search files",
"clear_search": "Clear search", "clear_search": "Clear search",
@ -87,6 +88,7 @@
"syncing_list": "Syncing...", "syncing_list": "Syncing...",
"sync_success": "List synced", "sync_success": "List synced",
"sync_failed": "Sync failed, please refresh the page", "sync_failed": "Sync failed, please refresh the page",
"sync_retry": "Resync",
"sync_not_supported": "Auto sync is not supported on this platform. Please refresh the page.", "sync_not_supported": "Auto sync is not supported on this platform. Please refresh the page.",
"preview_empty": "No files to preview", "preview_empty": "No files to preview",
"preview_loading": "Generating preview...", "preview_loading": "Generating preview...",

查看文件

@ -11,6 +11,7 @@
"loading_files": "正在加载文件...", "loading_files": "正在加载文件...",
"error_title": "错误", "error_title": "错误",
"retry": "重试", "retry": "重试",
"back": "返回",
"close": "关闭", "close": "关闭",
"search_placeholder": "搜索文件名", "search_placeholder": "搜索文件名",
"clear_search": "清除搜索", "clear_search": "清除搜索",
@ -87,6 +88,7 @@
"syncing_list": "同步中...", "syncing_list": "同步中...",
"sync_success": "已同步列表", "sync_success": "已同步列表",
"sync_failed": "同步失败,可能需要手动刷新页面", "sync_failed": "同步失败,可能需要手动刷新页面",
"sync_retry": "重新同步",
"sync_not_supported": "当前平台暂不支持自动同步,请手动刷新页面", "sync_not_supported": "当前平台暂不支持自动同步,请手动刷新页面",
"preview_empty": "暂无文件可预览", "preview_empty": "暂无文件可预览",
"preview_loading": "生成预览中...", "preview_loading": "生成预览中...",

查看文件

@ -11,6 +11,7 @@
"loading_files": "正在載入檔案...", "loading_files": "正在載入檔案...",
"error_title": "錯誤", "error_title": "錯誤",
"retry": "重試", "retry": "重試",
"back": "返回",
"close": "關閉", "close": "關閉",
"search_placeholder": "搜尋檔案名稱", "search_placeholder": "搜尋檔案名稱",
"clear_search": "清除搜尋", "clear_search": "清除搜尋",
@ -87,6 +88,7 @@
"syncing_list": "同步中...", "syncing_list": "同步中...",
"sync_success": "已同步列表", "sync_success": "已同步列表",
"sync_failed": "同步失敗,可能需要手動重新整理頁面", "sync_failed": "同步失敗,可能需要手動重新整理頁面",
"sync_retry": "重新同步",
"sync_not_supported": "目前平台暫不支援自動同步,請手動重新整理頁面", "sync_not_supported": "目前平台暫不支援自動同步,請手動重新整理頁面",
"preview_empty": "暫無檔案可預覽", "preview_empty": "暫無檔案可預覽",
"preview_loading": "產生預覽中...", "preview_loading": "產生預覽中...",

查看文件

@ -10,7 +10,7 @@
* 1. Edit version in package.json * 1. Edit version in package.json
* 2. Run: npm run sync:version (or automatically via prebuild/predev) * 2. Run: npm run sync:version (or automatically via prebuild/predev)
* *
* Generated at: 2025-12-29T06:17:04.003Z * Generated at: 2025-12-29T07:41:42.086Z
*/ */
/** /**

查看文件

@ -55,5 +55,7 @@ export interface FileSelectorEvents {
'type-filter': { type: FileType | 'all' }; 'type-filter': { type: FileType | 'all' };
'config-change': RuleConfig; 'config-change': RuleConfig;
'execute': void; 'execute': void;
'retry': void;
'back': void;
'panel-close': void; 'panel-close': void;
} }