From 2501f9db8522fe8ede695518a9091a0f079df04a Mon Sep 17 00:00:00 2001 From: liangxu Date: Wed, 29 Apr 2026 17:12:20 +0800 Subject: [PATCH] feat(desktop/renderer): surface re-authorize affordance for expired accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the generic 重新校验 action with state-aware copy and an arrow icon when authState is expired, and route the click through the bind flow so users land directly on the platform login page. AiPlatformsView gains the same shortcut and an inline alert explaining tasks are paused until re-auth completes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/renderer/views/AccountsView.vue | 78 ++++++++++++++----- .../src/renderer/views/AiPlatformsView.vue | 33 +++++++- 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/apps/desktop-client/src/renderer/views/AccountsView.vue b/apps/desktop-client/src/renderer/views/AccountsView.vue index 8651e5a..d8dd2dd 100644 --- a/apps/desktop-client/src/renderer/views/AccountsView.vue +++ b/apps/desktop-client/src/renderer/views/AccountsView.vue @@ -6,7 +6,7 @@ import StatusBadge from "../components/StatusBadge.vue"; import SurfaceCard from "../components/SurfaceCard.vue"; import { useDesktopRuntime } from "../composables/useDesktopRuntime"; import { showClientActionError } from "../lib/client-errors"; -import { formatDateTime, formatRelativeTime, formatVerifiedAtLabel, titleCaseToken } from "../lib/formatters"; +import { formatDateTime, formatRelativeTime, formatVerifiedAtLabel } from "../lib/formatters"; import { desktopPublishMediaCatalog } from "../lib/media-catalog"; import type { RuntimeAccount } from "../types"; @@ -18,6 +18,7 @@ const searchQuery = ref(""); const bindPendingPlatformId = ref(null); const consolePendingAccountId = ref(null); const unbindPendingAccountId = ref(null); +const reauthorizePendingAccountId = ref(null); const probePendingAccountIds = ref(new Set()); const actionSuccess = ref(null); @@ -59,8 +60,8 @@ function platformMeta(platform: string) { return ( desktopPublishMediaCatalog.find((item) => item.id === platform) ?? { id: platform, - label: titleCaseToken(platform), - shortName: titleCaseToken(platform).slice(0, 1), + label: "未知平台", + shortName: "未", accent: "#64748b", loginUrl: null, logoUrl: null, @@ -150,6 +151,15 @@ function sessionStateLabel(account: AccountRow): string { } function verificationTimeLabel(account: AccountRow): string { + switch (account.authState) { + case "expired": + return "需要重新授权"; + case "revoked": + return "已停用"; + case "challenge_required": + return account.authReason === "risk_control" ? "触发风控,需处理" : "需要人工验证"; + } + if (account.lastVerifiedAt) { return formatVerifiedAtLabel(account.lastVerifiedAt); } @@ -162,6 +172,16 @@ function verificationTimeLabel(account: AccountRow): string { return "尚未校验"; } +function accountRecoveryActionLabel(account: AccountRow): string { + return authState(account) === "expired" ? "重新授权" : "重新校验"; +} + +function isRecoveryActionPending(account: AccountRow): boolean { + return authState(account) === "expired" + ? reauthorizePendingAccountId.value === account.id || bindPendingPlatformId.value === account.platform + : isProbePending(account); +} + const filteredAccounts = computed(() => { const keyword = searchQuery.value.trim().toLowerCase(); @@ -272,6 +292,26 @@ function isProbePending(account: AccountRow): boolean { ); } +async function recoverAccount(account: AccountRow) { + if (authState(account) !== "expired") { + await verifyAccount(account); + return; + } + + reauthorizePendingAccountId.value = account.id; + actionSuccess.value = null; + + try { + const nextAccount = await window.desktopBridge.app.bindPublishAccount(account.platform); + actionSuccess.value = `${nextAccount.display_name} 已重新授权`; + await refresh(); + } catch (error) { + showClientActionError("bind-account", error); + } finally { + reauthorizePendingAccountId.value = null; + } +} + const tableVirtual = computed(() => filteredAccounts.value.length > 20); const tableScroll = computed(() => (tableVirtual.value ? { x: 960, y: 640 } : { x: 960 })); @@ -290,7 +330,7 @@ const tableColumns = [
-

MEDIA ACCOUNT MANAGEMENT

+

媒体授权中心

媒体账号管理

@@ -320,7 +360,7 @@ const tableColumns = [
- +

选择平台进行授权

@@ -348,7 +388,6 @@ const tableColumns = [

{{ platform.count > 0 ? `${platform.count} 个账号` : "未绑定" }}

-
- +
@@ -394,7 +433,7 @@ const tableColumns = [

授权账号列表

- +
所有平台 @@ -487,9 +526,12 @@ const tableColumns = [ - - - + + + @@ -517,7 +559,7 @@ const tableColumns = [ max-width: 1400px; } -/* Hero Section */ +/* 顶部信息区 */ .hero-card { background: #ffffff; border-radius: 20px; @@ -565,7 +607,7 @@ const tableColumns = [ font-weight: 600; } -/* Stats Strip */ +/* 统计条 */ .stats-strip { display: flex; padding: 24px 40px; @@ -595,7 +637,7 @@ const tableColumns = [ font-feature-settings: "tnum"; } -/* Content Section */ +/* 内容区 */ .content-section { display: flex; flex-direction: column; @@ -626,7 +668,7 @@ const tableColumns = [ gap: 20px; } -/* Modern Card Layout */ +/* 卡片布局 */ .modern-card { display: flex; flex-direction: column; @@ -659,7 +701,7 @@ const tableColumns = [ display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 8px; /* Reduced because content goes into footer */ + margin-bottom: 8px; } .brand-info { @@ -726,7 +768,7 @@ const tableColumns = [ height: 34px; } -/* Modern Enterprise Table Styles */ +/* 表格样式 */ :deep(.modern-table) { padding: 0 32px 32px; } @@ -842,7 +884,7 @@ const tableColumns = [ align-items: center; } -/* Responsiveness */ +/* 响应式 */ @media (max-width: 1024px) { .hero-header { flex-direction: column; diff --git a/apps/desktop-client/src/renderer/views/AiPlatformsView.vue b/apps/desktop-client/src/renderer/views/AiPlatformsView.vue index cb6e43c..9199dc5 100644 --- a/apps/desktop-client/src/renderer/views/AiPlatformsView.vue +++ b/apps/desktop-client/src/renderer/views/AiPlatformsView.vue @@ -95,7 +95,15 @@ function authColor(account: AccountRow | null) { } function accountActionAlert(account: AccountRow | null): string | null { - if (!account || account.authState !== "challenge_required") { + if (!account) { + return null; + } + + if (account.authState === "expired") { + return "当前账号授权已过期,相关任务会暂停执行。请点击“重新授权”完成登录后再继续。"; + } + + if (account.authState !== "challenge_required") { return null; } @@ -111,6 +119,15 @@ function accountActionAlert(account: AccountRow | null): string | null { } function verificationLabel(account: AccountRow): string { + switch (account.authState) { + case "expired": + return "需要重新授权"; + case "revoked": + return "已停用"; + case "challenge_required": + return account.authReason === "risk_control" ? "触发风控,需处理" : "需要人工验证"; + } + if (account.lastVerifiedAt) { return formatVerifiedAtLabel(account.lastVerifiedAt); } @@ -141,6 +158,10 @@ function sessionLabel(account: AccountRow): string { } } +function isReauthorizePending(account: AccountRow): boolean { + return bindPendingPlatformId.value === account.platform; +} + function onlineLabel(account: AccountRow): string { return account.online ? "客户端在线" : "客户端离线"; } @@ -307,6 +328,16 @@ async function unbindPlatform(account: AccountRow) {
+ + + + +