feat(publish-dedup): disable already-published accounts and rework retry UX
In the admin publish modal, fetch the article's publish records, disable accounts that already succeeded for the same article/platform, label queued/publishing accounts inline, and route the success notification by created vs existing task counts (with a dedicated warning for the `desktop_publish_duplicate` server error). Replace the "再次发送" mutation/popconfirm on both admin and desktop publish management views with a "重新创建发布任务" action that calls the same Create endpoint, only enables on failed/aborted/unknown rows, and reports created vs existing task counts (with a tailored warning for `unknown` results that may have already succeeded). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,7 @@ const navItems = computed(() => {
|
||||
{
|
||||
to: '/publish-management',
|
||||
title: '发布管理',
|
||||
description: '查看待发布队列、历史发送结果,并对文章再次发送',
|
||||
description: '查看待发布队列、历史发送结果和账号工作台',
|
||||
count: data?.summary.queuedTasks ?? 0,
|
||||
icon: SendOutlined,
|
||||
},
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
DesktopOutlined,
|
||||
ExportOutlined,
|
||||
ReloadOutlined,
|
||||
RetweetOutlined,
|
||||
SearchOutlined,
|
||||
SendOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import type { DesktopPublishTaskListResponse, DesktopTaskInfo, JsonValue } from '@geo/shared-types'
|
||||
import { notification } from 'ant-design-vue'
|
||||
@@ -302,12 +302,12 @@ function summaryForTask(
|
||||
}
|
||||
return '文章已成功发送到目标平台。'
|
||||
case 'failed':
|
||||
return '本次发送失败,可以手动再次发送。'
|
||||
return '本次发送失败,请检查账号状态或平台提示后重新创建发布。'
|
||||
case 'aborted':
|
||||
return '任务已被取消,如需继续可再次发送。'
|
||||
return '任务已被取消,请检查原因后重新创建发布。'
|
||||
case 'unknown':
|
||||
default:
|
||||
return '发送结果异常,已按失败处理,可检查平台侧结果后再次发送。'
|
||||
return '发送结果异常,已按失败处理,请检查平台侧结果后重新创建发布。'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,9 +326,9 @@ const searchTitle = ref('')
|
||||
const appliedTitle = ref('')
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const actionPendingTaskId = ref<string | null>(null)
|
||||
const consolePendingTaskId = ref<string | null>(null)
|
||||
const copiedErrorTaskId = ref<string | null>(null)
|
||||
const retryingTaskId = ref<string | null>(null)
|
||||
|
||||
function notifySuccess(message: string) {
|
||||
notification.success({
|
||||
@@ -422,22 +422,6 @@ function unbindPublishLeaseListener() {
|
||||
runtimeInvalidationUnsubscribe = null
|
||||
}
|
||||
|
||||
async function retryTask(taskId: string) {
|
||||
actionPendingTaskId.value = taskId
|
||||
|
||||
try {
|
||||
await window.desktopBridge.app.retryPublishTask(taskId)
|
||||
notifySuccess('已重新加入发布队列')
|
||||
await refreshTasks()
|
||||
} catch (err) {
|
||||
notifyActionError(
|
||||
normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '重新发送失败')) ?? '重新发送失败',
|
||||
)
|
||||
} finally {
|
||||
actionPendingTaskId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function openExternalUrl(url: string | null) {
|
||||
if (!url) {
|
||||
return
|
||||
@@ -473,6 +457,39 @@ async function openAccountWorkbench(record: PublishTaskItem) {
|
||||
}
|
||||
}
|
||||
|
||||
function canRetryTask(record: PublishTaskItem): boolean {
|
||||
return record.status === 'failed' || record.status === 'aborted' || record.status === 'unknown'
|
||||
}
|
||||
|
||||
async function retryTask(record: PublishTaskItem) {
|
||||
retryingTaskId.value = record.id
|
||||
|
||||
try {
|
||||
const result = await window.desktopBridge.app.retryPublishTask(record.id)
|
||||
const created = result.created_task_ids.length
|
||||
const existing = result.existing_task_ids.length
|
||||
if (created > 0) {
|
||||
notifySuccess(created === 1 ? '已重新创建发布任务' : `已重新创建 ${created} 个发布任务`)
|
||||
} else if (existing > 0) {
|
||||
if (record.status === 'unknown') {
|
||||
notifyActionError('原任务结果未知且已有发布记录,可能已成功,请先核实平台侧结果。')
|
||||
} else {
|
||||
notifySuccess('发布任务已存在,无需重复创建')
|
||||
}
|
||||
} else {
|
||||
notifySuccess('已提交重试请求')
|
||||
}
|
||||
await refreshTasks(1)
|
||||
} catch (err) {
|
||||
notifyActionError(
|
||||
normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '重新创建发布任务失败')) ??
|
||||
'重新创建发布任务失败',
|
||||
)
|
||||
} finally {
|
||||
retryingTaskId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function copyTextToClipboard(text: string): Promise<void> {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
@@ -846,26 +863,18 @@ const tableScroll = { x: 1080 } as const
|
||||
<template #icon><DesktopOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
v-if="!isPendingStatus(record.status)"
|
||||
title="确定要重试本次发布吗?"
|
||||
placement="topRight"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="retryTask(record.id)"
|
||||
>
|
||||
<a-tooltip title="再次发送" placement="top">
|
||||
<a-button
|
||||
type="text"
|
||||
class="icon-action-btn"
|
||||
:loading="actionPendingTaskId === record.id"
|
||||
:aria-label="`再次发送:${record.title}`"
|
||||
>
|
||||
<template #icon><SendOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
<a-tooltip v-else title="排队中...">
|
||||
<a-tooltip v-if="canRetryTask(record)" title="重新创建发布任务" placement="top">
|
||||
<a-button
|
||||
type="text"
|
||||
class="icon-action-btn"
|
||||
:loading="retryingTaskId === record.id"
|
||||
:aria-label="`重新创建发布任务:${record.title}`"
|
||||
@click.stop="retryTask(record)"
|
||||
>
|
||||
<template #icon><RetweetOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="isPendingStatus(record.status)" title="排队中...">
|
||||
<div class="action-placeholder-icon">
|
||||
<ClockCircleOutlined />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user