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">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
|
CopyOutlined,
|
||||||
ExportOutlined,
|
ExportOutlined,
|
||||||
|
LinkOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
@@ -164,6 +166,8 @@ const loading = ref(false);
|
|||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
const actionError = ref<string | null>(null);
|
const actionError = ref<string | null>(null);
|
||||||
const actionPendingTaskId = 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);
|
const actionMessage = ref<string | null>(null);
|
||||||
|
|
||||||
async function refreshTasks(page = currentPage.value) {
|
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() {
|
function applySearch() {
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
appliedTitle.value = searchTitle.value.trim();
|
appliedTitle.value = searchTitle.value.trim();
|
||||||
@@ -260,8 +320,11 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const accountNameMap = computed(() => {
|
const accountNameMap = computed(() => {
|
||||||
const entries = snapshot.value?.accounts ?? [];
|
return new Map((snapshot.value?.accounts ?? []).map((item) => [item.id, item.displayName]));
|
||||||
return new Map(entries.map((item) => [item.id, item.displayName]));
|
});
|
||||||
|
|
||||||
|
const accountMap = computed(() => {
|
||||||
|
return new Map((snapshot.value?.accounts ?? []).map((item) => [item.id, item]));
|
||||||
});
|
});
|
||||||
|
|
||||||
const publishTasks = computed<PublishTaskItem[]>(() =>
|
const publishTasks = computed<PublishTaskItem[]>(() =>
|
||||||
@@ -440,7 +503,24 @@ const tableColumns = [
|
|||||||
<div class="cell-primary-secondary meta-cell">
|
<div class="cell-primary-secondary meta-cell">
|
||||||
<div class="info-stack">
|
<div class="info-stack">
|
||||||
<span class="subtitle strong-text">{{ record.summary }}</span>
|
<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">
|
<span class="subtitle mono-detail">
|
||||||
尝试 {{ record.attempts }} 次
|
尝试 {{ record.attempts }} 次
|
||||||
<template v-if="record.leaseExpiresAt"> · 租约 {{ formatRelativeTime(record.leaseExpiresAt) }} 到期</template>
|
<template v-if="record.leaseExpiresAt"> · 租约 {{ formatRelativeTime(record.leaseExpiresAt) }} 到期</template>
|
||||||
@@ -461,15 +541,16 @@ const tableColumns = [
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="record.externalManageUrl">
|
<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
|
<a
|
||||||
class="external-task-link"
|
class="external-task-link"
|
||||||
:href="record.externalManageUrl"
|
:class="{ 'is-loading': consolePendingTaskId === record.id }"
|
||||||
target="_blank"
|
href="#"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@click.prevent.stop="openExternalUrl(record.externalManageUrl)"
|
:aria-busy="consolePendingTaskId === record.id"
|
||||||
|
@click.prevent.stop="openAccountWorkbench(record)"
|
||||||
>
|
>
|
||||||
<ExportOutlined />
|
<LinkOutlined />
|
||||||
打开管理页
|
打开管理页
|
||||||
</a>
|
</a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|||||||
Reference in New Issue
Block a user