feat(desktop-client): open account workbench and copy errors from publish list

Replace the raw external URL link with an in-app shortcut that opens the
bound account's platform console window, and add a one-click copy button
for failure error messages (with truncation + tooltip preview) so long
adapter errors stay readable in the task table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 12:21:20 +08:00
parent e1a1916bc5
commit aed8b20973
@@ -1,7 +1,9 @@
<script setup lang="ts">
import {
ClockCircleOutlined,
CopyOutlined,
ExportOutlined,
LinkOutlined,
ReloadOutlined,
SearchOutlined,
SendOutlined,
@@ -164,6 +166,8 @@ const loading = ref(false);
const error = ref<string | null>(null);
const actionError = ref<string | null>(null);
const actionPendingTaskId = ref<string | null>(null);
const consolePendingTaskId = ref<string | null>(null);
const copiedErrorTaskId = ref<string | null>(null);
const actionMessage = ref<string | null>(null);
async function refreshTasks(page = currentPage.value) {
@@ -222,6 +226,62 @@ async function openExternalUrl(url: string | null) {
}
}
async function openAccountWorkbench(record: PublishTaskItem) {
const account = accountMap.value.get(record.accountId) ?? null;
consolePendingTaskId.value = record.id;
actionError.value = null;
try {
await window.desktopBridge.app.openPublishAccountConsole({
id: record.accountId,
platform: record.platform,
platformUid: account?.platformUid ?? "",
displayName: account?.displayName ?? record.accountName,
});
} catch (err) {
actionError.value = err instanceof Error ? err.message : "打开账号工作台失败";
} finally {
consolePendingTaskId.value = null;
}
}
async function copyTextToClipboard(text: string): Promise<void> {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "true");
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}
async function copyErrorMessage(record: PublishTaskItem) {
if (!record.errorMessage) {
return;
}
actionError.value = null;
try {
await copyTextToClipboard(record.errorMessage);
copiedErrorTaskId.value = record.id;
actionMessage.value = "错误详情已复制。";
window.setTimeout(() => {
if (copiedErrorTaskId.value === record.id) {
copiedErrorTaskId.value = null;
}
}, 1_500);
} catch (err) {
actionError.value = err instanceof Error ? err.message : "复制错误详情失败";
}
}
function applySearch() {
currentPage.value = 1;
appliedTitle.value = searchTitle.value.trim();
@@ -260,8 +320,11 @@ onUnmounted(() => {
});
const accountNameMap = computed(() => {
const entries = snapshot.value?.accounts ?? [];
return new Map(entries.map((item) => [item.id, item.displayName]));
return new Map((snapshot.value?.accounts ?? []).map((item) => [item.id, item.displayName]));
});
const accountMap = computed(() => {
return new Map((snapshot.value?.accounts ?? []).map((item) => [item.id, item]));
});
const publishTasks = computed<PublishTaskItem[]>(() =>
@@ -440,7 +503,24 @@ const tableColumns = [
<div class="cell-primary-secondary meta-cell">
<div class="info-stack">
<span class="subtitle strong-text">{{ record.summary }}</span>
<span v-if="record.errorMessage" class="error-text">{{ record.errorMessage }}</span>
<span v-if="record.errorMessage" class="error-message-row">
<a-tooltip placement="topLeft" overlay-class-name="publish-error-tooltip">
<template #title>
<pre class="error-tooltip-content">{{ record.errorMessage }}</pre>
</template>
<span class="error-text error-text--truncate">{{ record.errorMessage }}</span>
</a-tooltip>
<a-tooltip :title="copiedErrorTaskId === record.id ? '已复制' : '复制错误详情'" placement="top">
<button
class="copy-error-btn"
type="button"
:aria-label="copiedErrorTaskId === record.id ? '错误详情已复制' : '复制错误详情'"
@click.stop="copyErrorMessage(record)"
>
<CopyOutlined />
</button>
</a-tooltip>
</span>
<span class="subtitle mono-detail">
尝试 {{ record.attempts }}
<template v-if="record.leaseExpiresAt"> · 租约 {{ formatRelativeTime(record.leaseExpiresAt) }} 到期</template>
@@ -461,15 +541,16 @@ const tableColumns = [
</template>
<template v-else-if="record.externalManageUrl">
·
<a-tooltip :title="record.externalManageUrl" overlay-class-name="external-url-tooltip">
<a-tooltip :title="`打开 ${record.accountName} 的${translatePlatform(record.platform)}工作台`" overlay-class-name="external-url-tooltip">
<a
class="external-task-link"
:href="record.externalManageUrl"
target="_blank"
:class="{ 'is-loading': consolePendingTaskId === record.id }"
href="#"
rel="noopener noreferrer"
@click.prevent.stop="openExternalUrl(record.externalManageUrl)"
:aria-busy="consolePendingTaskId === record.id"
@click.prevent.stop="openAccountWorkbench(record)"
>
<ExportOutlined />
<LinkOutlined />
打开管理页
</a>
</a-tooltip>