fix: improve session card favorite UX

This commit is contained in:
liujing
2026-07-20 15:46:54 +08:00
parent d49cc8a1c2
commit deea3cf512
+73 -32
View File
@@ -67,16 +67,6 @@
{{ statusLabel(s.status) }} {{ statusLabel(s.status) }}
</span> </span>
<span class="id-preview mono">会话 ID: <span class="id-highlight">{{ (s.id || '').substring(0, 15) }}...</span></span> <span class="id-preview mono">会话 ID: <span class="id-highlight">{{ (s.id || '').substring(0, 15) }}...</span></span>
<button class="name-btn-card" @click.stop="openNameEditor(s)" title="自定义名称"></button>
<button
class="fav-btn-card"
:class="{ 'is-fav': s.is_favorite }"
:disabled="favoritePending"
@click.stop="toggleFavorite(s)"
:title="s.is_favorite ? '取消收藏' : '收藏'"
:aria-label="s.is_favorite ? '取消收藏此会话' : '收藏此会话'"
:aria-pressed="String(s.is_favorite)"
>{{ s.is_favorite ? '★' : '☆' }}</button>
</div> </div>
<h4 class="card-summary">{{ s.custom_name || s.summary || '(无消息)' }}</h4> <h4 class="card-summary">{{ s.custom_name || s.summary || '(无消息)' }}</h4>
<div v-if="s.custom_name" class="card-original-summary">{{ s.summary }}</div> <div v-if="s.custom_name" class="card-original-summary">{{ s.summary }}</div>
@@ -85,21 +75,40 @@
<span class="path-text">{{ s.cwd || '-' }}</span> <span class="path-text">{{ s.cwd || '-' }}</span>
</div> </div>
</div> </div>
<div class="card-meta"> <div class="card-side">
<div class="meta-col"> <div class="card-meta">
<span class="meta-col-label">对应版本</span> <div class="meta-col">
<span class="meta-col-val">{{ s.cli_version || s.model || '-' }}</span> <span class="meta-col-label">对应版本</span>
<span class="meta-col-val">{{ s.cli_version || s.model || '-' }}</span>
</div>
<div class="meta-col">
<span class="meta-col-label">工具调用</span>
<span class="meta-col-val calls" :class="callsClass(s.tool_call_count)">{{ s.tool_call_count || 0 }} </span>
</div>
<div class="meta-col">
<span class="meta-col-label">开始时间</span>
<span class="meta-col-val time">{{ formatTimestamp(s.created_at) }}</span>
</div>
<div class="meta-col">
<button class="detail-btn" @click.stop="goToDetailPage(s)" title="进入详情页">详情 </button>
</div>
</div> </div>
<div class="meta-col"> <div class="card-actions">
<span class="meta-col-label">工具调用</span> <button class="action-btn" @click.stop="openNameEditor(s)" title="编辑名称" aria-label="编辑自定义名称">
<span class="meta-col-val calls" :class="callsClass(s.tool_call_count)">{{ s.tool_call_count || 0 }} </span> <span class="icon"></span><span class="text">编辑</span>
</div> </button>
<div class="meta-col"> <button
<span class="meta-col-label">开始时间</span> class="action-btn fav"
<span class="meta-col-val time">{{ formatTimestamp(s.created_at) }}</span> :class="{ 'is-fav': s.is_favorite }"
</div> :disabled="isFavoritePending(s)"
<div class="meta-col"> @click.stop="toggleFavorite(s)"
<button class="detail-btn" @click.stop="goToDetailPage(s)" title="进入详情页">详情 </button> :title="s.is_favorite ? '取消收藏' : '收藏'"
:aria-label="s.is_favorite ? '取消收藏此会话' : '收藏此会话'"
:aria-pressed="String(s.is_favorite)"
>
<span class="icon">{{ s.is_favorite ? '★' : '☆' }}</span>
<span class="text">{{ isFavoritePending(s) ? '处理中' : (s.is_favorite ? '已收藏' : '收藏') }}</span>
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -137,7 +146,7 @@
<button <button
class="fav-btn-drawer" class="fav-btn-drawer"
:class="{ 'is-fav': drawerSession.is_favorite }" :class="{ 'is-fav': drawerSession.is_favorite }"
:disabled="favoritePending" :disabled="isFavoritePending(drawerSession)"
@click.stop="toggleFavorite(drawerSession)" @click.stop="toggleFavorite(drawerSession)"
:title="drawerSession.is_favorite ? '取消收藏' : '收藏'" :title="drawerSession.is_favorite ? '取消收藏' : '收藏'"
:aria-label="drawerSession.is_favorite ? '取消收藏此会话' : '收藏此会话'" :aria-label="drawerSession.is_favorite ? '取消收藏此会话' : '收藏此会话'"
@@ -276,7 +285,18 @@ const statusOpts = [
] ]
const favoriteOnly = ref(false) const favoriteOnly = ref(false)
const favoritePending = ref(false) // global lock: one favorite mutation at a time const favoritePendingMap = ref({})
function isFavoritePending(s) {
return !!favoritePendingMap.value[`${s.engine}:${s.id}`]
}
function setFavoritePending(s, val) {
if (val) {
favoritePendingMap.value[`${s.engine}:${s.id}`] = true
} else {
delete favoritePendingMap.value[`${s.engine}:${s.id}`]
}
}
const engineDist = computed(() => { const engineDist = computed(() => {
const dist = {} const dist = {}
@@ -494,8 +514,8 @@ async function generateDrawerTitle() {
} }
function toggleFavorite(s) { function toggleFavorite(s) {
if (!s || favoritePending.value) return if (!s || isFavoritePending(s)) return
favoritePending.value = true setFavoritePending(s, true)
const action = s.is_favorite ? unfavoriteSession : favoriteSession const action = s.is_favorite ? unfavoriteSession : favoriteSession
action(s.engine, s.id) action(s.engine, s.id)
@@ -508,14 +528,23 @@ function toggleFavorite(s) {
drawerSession.value.is_favorite = result.is_favorite drawerSession.value.is_favorite = result.is_favorite
drawerSession.value.favorited_at = result.favorited_at drawerSession.value.favorited_at = result.favorited_at
} }
page.value = 1 if (favoriteOnly.value && !result.is_favorite) {
return loadSessions() const idx = sessions.value.findIndex(x => x.id === s.id && x.engine === s.engine)
if (idx !== -1) {
sessions.value.splice(idx, 1)
total.value = Math.max(0, total.value - 1)
if (displayedSessions.value.length === 0 && page.value > 1) {
page.value--
return loadSessions()
}
}
}
}) })
.catch((err) => { .catch((err) => {
ElMessage.error('收藏操作失败: ' + (err.message || '')) ElMessage.error('收藏操作失败: ' + (err.message || ''))
}) })
.finally(() => { .finally(() => {
favoritePending.value = false setFavoritePending(s, false)
}) })
} }
@@ -591,12 +620,22 @@ onMounted(loadSessions)
.id-highlight { color: var(--accent-light); } .id-highlight { color: var(--accent-light); }
.name-btn-card { background: none; border: none; font-size: 12px; cursor: pointer; opacity: 0.4; padding: 0 2px; } .name-btn-card { background: none; border: none; font-size: 12px; cursor: pointer; opacity: 0.4; padding: 0 2px; }
.name-btn-card:hover { opacity: 1; } .name-btn-card:hover { opacity: 1; }
.card-summary { font-size: 13px; font-weight: 500; color: var(--text-primary); line-height: 1.4; margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .card-summary { font-size: 1rem; font-weight: 700; color: var(--text-primary); line-height: 1.4; margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card-original-summary { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; } .card-original-summary { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.card-path { display: flex; align-items: center; gap: 4px; font-size: 10px; color: var(--text-muted); } .card-path { display: flex; align-items: center; gap: 4px; font-size: 10px; color: var(--text-muted); }
.path-ico { font-size: 12px; } .path-ico { font-size: 12px; }
.path-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'SF Mono', monospace; } .path-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'SF Mono', monospace; }
.card-side { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
.card-actions { display: flex; gap: 8px; flex-shrink: 0; align-items: center; }
.action-btn { display: inline-flex; align-items: center; justify-content: center; gap: 4px; min-width: 44px; min-height: 44px; padding: 4px 12px; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; background: transparent; transition: all 0.12s; border: 1px solid var(--border); color: var(--text-secondary); }
.action-btn:hover { border-color: var(--accent); color: var(--accent-light); }
.action-btn:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
.action-btn.fav { border-color: var(--border); }
.action-btn.fav:hover { border-color: #f59e0b; color: #f59e0b; }
.action-btn.fav.is-fav { border-color: #f59e0b; color: #f59e0b; background: rgba(245,158,11,0.1); }
.action-btn:disabled { opacity: 0.5; cursor: not-allowed; border-color: var(--border) !important; color: var(--text-muted) !important; background: transparent !important; }
.card-meta { display: flex; align-items: center; gap: 16px; flex-shrink: 0; } .card-meta { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
.meta-col { text-align: center; } .meta-col { text-align: center; }
.meta-col-label { display: block; font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; } .meta-col-label { display: block; font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; }
@@ -703,7 +742,9 @@ onMounted(loadSessions)
.card-main { padding-right: 0; width: 100%; } .card-main { padding-right: 0; width: 100%; }
.card-tags { gap: 4px; } .card-tags { gap: 4px; }
.id-preview { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; } .id-preview { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.card-side { flex-direction: column; align-items: flex-end; width: 100%; gap: 12px; }
.card-meta { width: 100%; justify-content: space-around; } .card-meta { width: 100%; justify-content: space-around; }
.card-actions { width: 100%; justify-content: flex-end; }
.drawer { max-width: 100%; } .drawer { max-width: 100%; }
} }
.footer-btn-detail { .footer-btn-detail {