fix(frontend): restore conversation delete animation
Use a two-phase delete flow for conversation list items. Instead of immediately removing the deleted conversation from the array and relying only on transition-group leave hooks, mark the target conversation as pending deletion first so it can play the left-slide animation, then remove it after the animation completes. Also keep the list container mounted during delete mode so empty/loading branches do not interrupt the exit transition. Validation: npm run build --silent 2>&1 | tail -n 20
This commit is contained in:
parent
08d9812f45
commit
b1cf4b9ff2
@ -58,6 +58,7 @@ export const computed = {
|
||||
'searchResults',
|
||||
'conversationInsertAnimations',
|
||||
'conversationListAnimationMode',
|
||||
'pendingDeletingConversationIds',
|
||||
'searchActive',
|
||||
'searchInProgress',
|
||||
'searchMoreAvailable',
|
||||
|
||||
@ -631,17 +631,31 @@ export const conversationMethods = {
|
||||
history.replaceState({}, '', '/new');
|
||||
}
|
||||
|
||||
// 先从本地列表移除,让侧边栏执行删除动画;随后再刷新服务端列表补齐分页状态
|
||||
// 先给目标项加 deleting 类执行左滑动画,再从本地列表移除。
|
||||
// 这比完全依赖 transition-group leave 更稳定:即使列表即将为空或分支切换,也能先播放离场动画。
|
||||
this.conversationListAnimationMode = 'delete';
|
||||
const deletingIds = Array.isArray(this.pendingDeletingConversationIds)
|
||||
? this.pendingDeletingConversationIds
|
||||
: [];
|
||||
if (!deletingIds.includes(conversationId)) {
|
||||
this.pendingDeletingConversationIds = [...deletingIds, conversationId];
|
||||
}
|
||||
await this.$nextTick();
|
||||
await waitForAnimation(430);
|
||||
|
||||
this.conversations = this.conversations.filter(
|
||||
(conversation) => conversation.id !== conversationId
|
||||
);
|
||||
this.searchResults = this.searchResults.filter(
|
||||
(conversation) => conversation.id !== conversationId
|
||||
);
|
||||
this.pendingDeletingConversationIds = (Array.isArray(this.pendingDeletingConversationIds)
|
||||
? this.pendingDeletingConversationIds
|
||||
: []
|
||||
).filter((id) => id !== conversationId);
|
||||
|
||||
// 等待“左滑离场 + 0.1s 后集体上移”动画结束,避免刷新列表打断过渡。
|
||||
await waitForAnimation(560);
|
||||
await waitForAnimation(180);
|
||||
|
||||
// 刷新对话列表
|
||||
this.conversationsOffset = 0;
|
||||
@ -656,6 +670,11 @@ export const conversationMethods = {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.pendingDeletingConversationIds = (Array.isArray(this.pendingDeletingConversationIds)
|
||||
? this.pendingDeletingConversationIds
|
||||
: []
|
||||
).filter((id) => id !== conversationId);
|
||||
this.conversationListAnimationMode = 'idle';
|
||||
console.error('删除对话异常:', error);
|
||||
this.uiPushToast({
|
||||
title: '删除对话异常',
|
||||
|
||||
@ -129,12 +129,12 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="loading && !displayConversations.length && !searchActive"
|
||||
v-if="loading && !displayConversations.length && !searchActive && !isDeletingConversation"
|
||||
class="loading-conversations"
|
||||
>
|
||||
正在加载...
|
||||
</div>
|
||||
<div v-else-if="!displayConversations.length && !loading" class="no-conversations">
|
||||
<div v-else-if="!displayConversations.length && !loading && !isDeletingConversation" class="no-conversations">
|
||||
{{ searchActive ? '未找到匹配对话' : '暂无对话记录' }}
|
||||
</div>
|
||||
<transition-group
|
||||
@ -153,7 +153,8 @@
|
||||
running: isConversationActive(conv.id) || isConversationCompleted(conv.id),
|
||||
'insert-from-left': conversationInsertAnimations[conv.id] === 'create',
|
||||
'insert-from-under': conversationInsertAnimations[conv.id] === 'duplicate',
|
||||
'duplicate-source-mask': conversationInsertAnimations[conv.id] === 'duplicateSource'
|
||||
'duplicate-source-mask': conversationInsertAnimations[conv.id] === 'duplicateSource',
|
||||
deleting: pendingDeletingConversationIds.includes(conv.id)
|
||||
}"
|
||||
@click="
|
||||
openActionMenuId = null;
|
||||
@ -338,6 +339,7 @@ const {
|
||||
conversations,
|
||||
conversationInsertAnimations,
|
||||
conversationListAnimationMode,
|
||||
pendingDeletingConversationIds,
|
||||
conversationsLoading,
|
||||
hasMoreConversations: hasMore,
|
||||
loadingMoreConversations: loadingMore,
|
||||
@ -375,6 +377,7 @@ const displayConversations = computed(() =>
|
||||
const loading = computed(() =>
|
||||
searchActive.value ? searchInProgress.value : conversationsLoading.value
|
||||
);
|
||||
const isDeletingConversation = computed(() => conversationListAnimationMode.value === 'delete');
|
||||
const runningTaskItems = computed(() =>
|
||||
Array.isArray(props.runningTasks)
|
||||
? props.runningTasks.filter(
|
||||
|
||||
@ -13,6 +13,7 @@ interface ConversationState {
|
||||
searchResults: ConversationSummary[];
|
||||
conversationInsertAnimations: Record<string, 'create' | 'duplicate' | 'duplicateSource'>;
|
||||
conversationListAnimationMode: 'idle' | 'create' | 'duplicate' | 'delete';
|
||||
pendingDeletingConversationIds: string[];
|
||||
conversationsLoading: boolean;
|
||||
hasMoreConversations: boolean;
|
||||
loadingMoreConversations: boolean;
|
||||
@ -37,6 +38,7 @@ export const useConversationStore = defineStore('conversation', {
|
||||
searchResults: [],
|
||||
conversationInsertAnimations: {},
|
||||
conversationListAnimationMode: 'idle',
|
||||
pendingDeletingConversationIds: [],
|
||||
conversationsLoading: false,
|
||||
hasMoreConversations: false,
|
||||
loadingMoreConversations: false,
|
||||
@ -60,6 +62,7 @@ export const useConversationStore = defineStore('conversation', {
|
||||
this.searchResults = [];
|
||||
this.conversationInsertAnimations = {};
|
||||
this.conversationListAnimationMode = 'idle';
|
||||
this.pendingDeletingConversationIds = [];
|
||||
this.searchActive = false;
|
||||
this.searchInProgress = false;
|
||||
this.searchMoreAvailable = false;
|
||||
|
||||
@ -394,6 +394,15 @@ body[data-theme='dark']
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.conversation-sidebar .conversation-item.deleting {
|
||||
transform: translateX(-112%);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
transform 420ms cubic-bezier(0.32, 0, 0.67, 0),
|
||||
opacity 260ms ease;
|
||||
}
|
||||
|
||||
.conversation-sidebar .conversation-item:hover,
|
||||
.conversation-sidebar .conversation-item:focus-within,
|
||||
.conversation-sidebar .conversation-item.active:hover,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user