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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user