feat(versioning-ui): 优化版本不一致提示与回溯限制

This commit is contained in:
JOJO 2026-04-11 17:01:35 +08:00
parent 6379f8c729
commit 55feb36091
7 changed files with 67 additions and 7 deletions

View File

@ -396,6 +396,7 @@
:detail-loading="versioningDetailLoading" :detail-loading="versioningDetailLoading"
:restoring="versioningRestoring" :restoring="versioningRestoring"
:restore-mode="versioningRestoreMode" :restore-mode="versioningRestoreMode"
:workspace-matched="versioningWorkspaceMatched"
@close="versioningDialogOpen = false" @close="versioningDialogOpen = false"
@refresh="refreshVersioningDialog" @refresh="refreshVersioningDialog"
@toggle-enabled="toggleConversationVersioning" @toggle-enabled="toggleConversationVersioning"

View File

@ -21,6 +21,7 @@ export const versioningMethods = {
this.versioningMode = 'overwrite'; this.versioningMode = 'overwrite';
this.versioningRestoreMode = 'overwrite'; this.versioningRestoreMode = 'overwrite';
this.versioningMismatch = !!payload.mismatch; this.versioningMismatch = !!payload.mismatch;
this.versioningWorkspaceMatched = payload.workspace_matched !== false;
return payload; return payload;
} catch (error) { } catch (error) {
if (!silent) { if (!silent) {
@ -66,6 +67,7 @@ export const versioningMethods = {
async openVersioningDialog() { async openVersioningDialog() {
if (!this.currentConversationId) { if (!this.currentConversationId) {
this.versioningEnabled = !!this.newConversationVersioningEnabled; this.versioningEnabled = !!this.newConversationVersioningEnabled;
this.versioningWorkspaceMatched = true;
this.versioningCheckpoints = []; this.versioningCheckpoints = [];
this.versioningSelectedSeq = null; this.versioningSelectedSeq = null;
this.versioningSelectedDetail = null; this.versioningSelectedDetail = null;
@ -224,7 +226,11 @@ export const versioningMethods = {
title: '版本状态不一致', title: '版本状态不一致',
message: '当前工作区与该对话最新版本不一致,是否恢复到最新一次提交?', message: '当前工作区与该对话最新版本不一致,是否恢复到最新一次提交?',
confirmText: '恢复', confirmText: '恢复',
cancelText: '保持当前' cancelText: '保持当前',
warningText: '警告:恢复操作会导致当前工作区内容被重置为当前对话的最新一次提交备份。',
closeOnBackdrop: false,
confirmVariant: 'danger',
confirmOnLeft: true
}); });
if (!confirmed) return; if (!confirmed) return;
try { try {

View File

@ -86,6 +86,7 @@ export function dataState() {
newConversationVersioningEnabled: false, newConversationVersioningEnabled: false,
versioningMode: 'overwrite', versioningMode: 'overwrite',
versioningMismatch: false, versioningMismatch: false,
versioningWorkspaceMatched: true,
versioningDialogOpen: false, versioningDialogOpen: false,
versioningLoading: false, versioningLoading: false,
versioningCheckpoints: [], versioningCheckpoints: [],

View File

@ -84,11 +84,14 @@
</div> </div>
<footer class="versioning-footer"> <footer class="versioning-footer">
<div v-if="workspaceMatched === false" class="workspace-mismatch-tip">
当前对话所属工作区与当前工作区不一致无法执行回溯
</div>
<label>回溯模式覆盖当前对话</label> <label>回溯模式覆盖当前对话</label>
<button <button
type="button" type="button"
class="confirm-btn" class="confirm-btn"
:disabled="!enabled || selectedSeq === null || selectedSeq === undefined || restoring" :disabled="!enabled || selectedSeq === null || selectedSeq === undefined || restoring || workspaceMatched === false"
@click="$emit('confirm')" @click="$emit('confirm')"
> >
{{ restoring ? '回溯中...' : '确认回溯' }} {{ restoring ? '回溯中...' : '确认回溯' }}
@ -111,6 +114,7 @@ const props = defineProps<{
detailLoading: boolean; detailLoading: boolean;
restoring: boolean; restoring: boolean;
restoreMode: string; restoreMode: string;
workspaceMatched?: boolean;
}>(); }>();
defineEmits([ defineEmits([
@ -187,6 +191,7 @@ const formatTime = (value: string) => {
.delta { display: inline-flex; gap: 8px; } .delta { display: inline-flex; gap: 8px; }
.loading, .empty { color: var(--claude-text-secondary); font-size: 13px; padding: 12px 4px; } .loading, .empty { color: var(--claude-text-secondary); font-size: 13px; padding: 12px 4px; }
.versioning-footer { padding: 12px 18px; display: flex; align-items: center; gap: 10px; justify-content: flex-end; } .versioning-footer { padding: 12px 18px; display: flex; align-items: center; gap: 10px; justify-content: flex-end; }
.workspace-mismatch-tip { margin-right: auto; color: #dc2626; font-size: 13px; font-weight: 600; }
.versioning-footer select { border: 1px solid var(--theme-control-border); border-radius: 8px; padding: 6px 10px; background: var(--theme-surface-soft); color: var(--claude-text); } .versioning-footer select { border: 1px solid var(--theme-control-border); border-radius: 8px; padding: 6px 10px; background: var(--theme-surface-soft); color: var(--claude-text); }
.confirm-btn { border: 0; background: var(--claude-accent); color: #fff; border-radius: 8px; padding: 8px 14px; cursor: pointer; } .confirm-btn { border: 0; background: var(--claude-accent); color: #fff; border-radius: 8px; padding: 8px 14px; cursor: pointer; }
.confirm-btn:disabled { opacity: .5; cursor: not-allowed; } .confirm-btn:disabled { opacity: .5; cursor: not-allowed; }

View File

@ -1,16 +1,33 @@
<template> <template>
<transition name="confirm-dialog-fade"> <transition name="confirm-dialog-fade">
<div class="confirm-overlay" v-if="dialog?.visible" @click.self="resolve(false)"> <div
class="confirm-overlay"
v-if="dialog?.visible"
@click.self="dialog?.closeOnBackdrop !== false && resolve(false)"
>
<div class="confirm-modal"> <div class="confirm-modal">
<div class="confirm-title">{{ dialog.title || '确认操作' }}</div> <div class="confirm-title">{{ dialog.title || '确认操作' }}</div>
<div class="confirm-message">{{ dialog.message }}</div> <div class="confirm-message">{{ dialog.message }}</div>
<div class="confirm-actions"> <div v-if="dialog.warningText" class="confirm-warning">{{ dialog.warningText }}</div>
<button type="button" class="confirm-button" @click="resolve(false)"> <div
class="confirm-actions"
:class="{ 'confirm-actions--confirm-left': dialog.confirmOnLeft }"
>
<button
type="button"
class="confirm-button confirm-button--cancel"
@click="resolve(false)"
>
{{ dialog.cancelText || '取消' }} {{ dialog.cancelText || '取消' }}
</button> </button>
<button <button
type="button" type="button"
class="confirm-button confirm-button--primary" class="confirm-button confirm-button--confirm"
:class="
dialog.confirmVariant === 'danger'
? 'confirm-button--danger'
: 'confirm-button--primary'
"
@click="resolve(true)" @click="resolve(true)"
> >
{{ dialog.confirmText || '确认' }} {{ dialog.confirmText || '确认' }}

View File

@ -31,6 +31,10 @@ interface ConfirmDialogOptions {
message?: string; message?: string;
confirmText?: string; confirmText?: string;
cancelText?: string; cancelText?: string;
warningText?: string;
closeOnBackdrop?: boolean;
confirmVariant?: 'primary' | 'danger';
confirmOnLeft?: boolean;
} }
interface ConfirmDialogState extends ConfirmDialogOptions { interface ConfirmDialogState extends ConfirmDialogOptions {
@ -258,7 +262,11 @@ export const useUiStore = defineStore('ui', {
title: options.title || '确认操作', title: options.title || '确认操作',
message: options.message || '请确认本次操作', message: options.message || '请确认本次操作',
confirmText: options.confirmText || '确认', confirmText: options.confirmText || '确认',
cancelText: options.cancelText || '取消' cancelText: options.cancelText || '取消',
warningText: options.warningText || '',
closeOnBackdrop: options.closeOnBackdrop !== false,
confirmVariant: options.confirmVariant === 'danger' ? 'danger' : 'primary',
confirmOnLeft: options.confirmOnLeft === true
}; };
this.pendingConfirmResolver = resolve; this.pendingConfirmResolver = resolve;
}); });

View File

@ -1898,6 +1898,14 @@
color: var(--claude-text-secondary); color: var(--claude-text-secondary);
} }
.confirm-warning {
margin-top: 10px;
font-size: 13px;
font-weight: 600;
color: #ef4444;
line-height: 1.5;
}
.confirm-actions { .confirm-actions {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@ -1905,6 +1913,14 @@
margin-top: 24px; margin-top: 24px;
} }
.confirm-actions--confirm-left .confirm-button--confirm {
order: 1;
}
.confirm-actions--confirm-left .confirm-button--cancel {
order: 2;
}
.confirm-button { .confirm-button {
border: 1px solid var(--theme-control-border-strong); border: 1px solid var(--theme-control-border-strong);
background: transparent; background: transparent;
@ -1921,6 +1937,12 @@
color: #fff; color: #fff;
} }
.confirm-button--danger {
background: #dc2626;
border-color: #b91c1c;
color: #fff;
}
.subagent-activity-overlay { .subagent-activity-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;