diff --git a/apps/admin-web/src/components/ArticlePublishStatus.vue b/apps/admin-web/src/components/ArticlePublishStatus.vue index 310b12f..9929692 100644 --- a/apps/admin-web/src/components/ArticlePublishStatus.vue +++ b/apps/admin-web/src/components/ArticlePublishStatus.vue @@ -19,7 +19,7 @@ interface PublishPlatformEntry { status: PublishRecordStatus; } -type PublishRecordStatus = "success" | "failed" | "publishing" | "pending_review"; +type PublishRecordStatus = "success" | "failed" | "publishing"; interface PublishPlatformSection { key: PublishRecordStatus; @@ -104,7 +104,7 @@ const failedEntries = computed(() => ); const pendingEntries = computed(() => - platformEntries.value.filter((entry) => entry.status === "publishing" || entry.status === "pending_review"), + platformEntries.value.filter((entry) => entry.status === "publishing"), ); const displayStatus = computed(() => { @@ -121,7 +121,7 @@ const displayStatus = computed(() => { } if (pendingEntries.value.length > 0) { - return "pending_review"; + return "publishing"; } if (props.status === "partial_success") { @@ -136,6 +136,10 @@ const displayStatus = computed(() => { return "failed"; } + if (props.status === "pending_review") { + return "failed"; + } + return props.status ?? null; }); @@ -149,8 +153,6 @@ const statusTone = computed(() => { return "failed"; case "publishing": return "processing"; - case "pending_review": - return "warning"; default: return "default"; } @@ -178,13 +180,9 @@ const popoverSections = computed(() => { } if (pendingEntries.value.length > 0) { - const pendingStatus = pendingEntries.value.some((entry) => entry.status === "publishing") - ? "publishing" - : "pending_review"; - sections.push({ - key: pendingStatus, - label: `${getPublishStatusMeta(pendingStatus).label}:`, + key: "publishing", + label: `${getPublishStatusMeta("publishing").label}:`, tone: "pending", entries: pendingEntries.value, }); @@ -208,7 +206,7 @@ function normalizePublishRecordStatus(status?: string | null): PublishRecordStat case "queued": return "publishing"; case "pending_review": - return "pending_review"; + return "failed"; case "failed": case "publish_failed": default: diff --git a/apps/admin-web/src/components/CustomArticleTab.vue b/apps/admin-web/src/components/CustomArticleTab.vue index c4649f2..05db7de 100644 --- a/apps/admin-web/src/components/CustomArticleTab.vue +++ b/apps/admin-web/src/components/CustomArticleTab.vue @@ -77,7 +77,6 @@ const publishStatusOptions = computed(() => [ { label: getPublishStatusMeta("success").label, value: "success" }, { label: getPublishStatusMeta("partial_success").label, value: "partial_success" }, { label: getPublishStatusMeta("failed").label, value: "failed" }, - { label: getPublishStatusMeta("pending_review").label, value: "pending_review" }, ]); const articleParams = computed(() => { diff --git a/apps/admin-web/src/lib/display.ts b/apps/admin-web/src/lib/display.ts index 1c040b0..25ef086 100644 --- a/apps/admin-web/src/lib/display.ts +++ b/apps/admin-web/src/lib/display.ts @@ -20,7 +20,7 @@ const publishStatusMap: Record = { published: { label: "status.publish.published", color: "success" }, publish_success: { label: "status.publish.publish_success", color: "success" }, publish_failed: { label: "status.publish.publish_failed", color: "error" }, - pending_review: { label: "status.publish.pending_review", color: "warning" }, + pending_review: { label: "status.publish.failed", color: "error" }, }; export function formatDateTime(value?: string | null, pattern = "YYYY-MM-DD HH:mm"): string { diff --git a/apps/admin-web/src/views/FreeCreateView.vue b/apps/admin-web/src/views/FreeCreateView.vue index 87d542a..8b92555 100644 --- a/apps/admin-web/src/views/FreeCreateView.vue +++ b/apps/admin-web/src/views/FreeCreateView.vue @@ -111,7 +111,6 @@ const publishStatusOptions = computed(() => [ { label: getPublishStatusMeta("success").label, value: "success" }, { label: getPublishStatusMeta("partial_success").label, value: "partial_success" }, { label: getPublishStatusMeta("failed").label, value: "failed" }, - { label: getPublishStatusMeta("pending_review").label, value: "pending_review" }, ]); const articleColumns = computed>(() => [ diff --git a/apps/admin-web/src/views/TemplatesView.vue b/apps/admin-web/src/views/TemplatesView.vue index 160bf34..65e1498 100644 --- a/apps/admin-web/src/views/TemplatesView.vue +++ b/apps/admin-web/src/views/TemplatesView.vue @@ -227,7 +227,6 @@ const publishStatusOptions = computed(() => [ { label: getPublishStatusMeta("success").label, value: "success" }, { label: getPublishStatusMeta("partial_success").label, value: "partial_success" }, { label: getPublishStatusMeta("failed").label, value: "failed" }, - { label: getPublishStatusMeta("pending_review").label, value: "pending_review" }, ]); const articleColumns = computed>(() => [ diff --git a/apps/desktop-client/src/main/runtime-controller.ts b/apps/desktop-client/src/main/runtime-controller.ts index f57d52b..3ca0eb7 100644 --- a/apps/desktop-client/src/main/runtime-controller.ts +++ b/apps/desktop-client/src/main/runtime-controller.ts @@ -1002,7 +1002,7 @@ function buildScaffoldResult( detail: string, ): AdapterExecutionResult { return { - status: "unknown", + status: "failed", payload: { ...adapterPayload, runtime_mode: "desktop-scaffold", @@ -1010,11 +1010,11 @@ function buildScaffoldResult( task_platform: task.platform, }, error: { - code: "desktop_task_scaffold_only", - message: "desktop runtime adapter is scaffold-only and requires later reconcile", + code: "desktop_task_adapter_missing", + message: "desktop runtime adapter is not implemented for this platform", detail, }, - summary: `${task.title} 已被 desktop runtime 消费,但当前 adapter 仍是 scaffold,仅回写 unknown 供后续 reconcile。`, + summary: `${task.title} 执行失败:当前平台的 desktop 发布适配器尚未实现。`, }; } @@ -1237,7 +1237,7 @@ function summaryFromTaskStatus(status: RuntimeTaskStatus): string { case "aborted": return "任务已被取消。"; default: - return "任务进入 unknown/reconcile 分支,等待进一步确认。"; + return "任务执行异常,已按失败处理。"; } } diff --git a/apps/desktop-client/src/renderer/views/PublishManagementView.vue b/apps/desktop-client/src/renderer/views/PublishManagementView.vue index f386cfe..f6c1a48 100644 --- a/apps/desktop-client/src/renderer/views/PublishManagementView.vue +++ b/apps/desktop-client/src/renderer/views/PublishManagementView.vue @@ -50,7 +50,7 @@ function statusLabel(status: DesktopTaskInfo["status"]): string { in_progress: "正在发送", succeeded: "发送成功", failed: "发送失败", - unknown: "待确认", + unknown: "发送失败", aborted: "已取消", }; return map[status]; @@ -93,8 +93,26 @@ function extractString(value: JsonValue | null | undefined): string | null { return trimmed ? trimmed : null; } -function summaryForTask(task: DesktopTaskInfo): string { - switch (task.status) { +function normalizePublishTaskStatus(status: DesktopTaskInfo["status"]): DesktopTaskInfo["status"] { + return status === "unknown" ? "failed" : status; +} + +function normalizeTaskErrorMessage(message: string | null): string | null { + const normalized = extractString(message); + if (!normalized) { + return null; + } + if (normalized.includes("scaffold-only") || normalized.includes("requires later reconcile")) { + return "当前平台的桌面发布适配器未实现,任务已按失败处理。"; + } + if (normalized.includes("adapter is not implemented for this platform")) { + return "当前平台的桌面发布适配器未实现,任务已按失败处理。"; + } + return normalized; +} + +function summaryForTask(status: DesktopTaskInfo["status"]): string { + switch (status) { case "queued": return "任务已进入队列,等前面的 PublishTasks 翻完后再继续展示历史记录。"; case "in_progress": @@ -107,7 +125,7 @@ function summaryForTask(task: DesktopTaskInfo): string { return "任务已被取消,如需继续可再次发送。"; case "unknown": default: - return "平台返回结果仍待确认,建议人工检查后再次发送。"; + return "发送结果异常,已按失败处理,可检查平台侧结果后再次发送。"; } } @@ -217,6 +235,7 @@ const accountNameMap = computed(() => { const publishTasks = computed(() => (taskPage.value?.items ?? []).map((task) => { + const normalizedStatus = normalizePublishTaskStatus(task.status); const payload = task.payload ?? {}; const result = task.result ?? {}; const taskError = task.error ?? {}; @@ -233,18 +252,19 @@ const publishTasks = computed(() => platform: task.platform, accountId: task.target_account_id, accountName: accountNameMap.value.get(task.target_account_id) ?? "待同步账号", - status: task.status, - summary: summaryForTask(task), + status: normalizedStatus, + summary: summaryForTask(normalizedStatus), attempts: task.attempts, leaseExpiresAt: task.lease_expires_at ? parseTimestamp(task.lease_expires_at) : null, createdAt: parseTimestamp(task.created_at), updatedAt: parseTimestamp(task.updated_at), externalArticleUrl: extractString(result.external_article_url), externalManageUrl: extractString(result.external_manage_url), - errorMessage: + errorMessage: normalizeTaskErrorMessage( extractString(taskError.message) ?? extractString(taskError.detail) ?? extractString(taskError.code), + ), }; }), ); diff --git a/apps/desktop-client/src/renderer/views/TasksView.vue b/apps/desktop-client/src/renderer/views/TasksView.vue index f6060e5..892f5c1 100644 --- a/apps/desktop-client/src/renderer/views/TasksView.vue +++ b/apps/desktop-client/src/renderer/views/TasksView.vue @@ -27,7 +27,7 @@ function translateTaskStatus(status: string) { in_progress: "执行中", succeeded: "成功", failed: "失败", - unknown: "未知异常", + unknown: "失败", aborted: "已取消", }; return map[status?.toLowerCase()] || status; @@ -53,7 +53,7 @@ const columns = computed(() => { { key: "exception", title: "异常与终态 (Terminal)", - description: "unknown、failed、succeeded 等终态或补偿态。", + description: "failed、succeeded、aborted 等终态任务。", items: tasks.filter((item) => !["queued", "in_progress"].includes(item.status)), }, ]; diff --git a/server/internal/tenant/app/desktop_task_service.go b/server/internal/tenant/app/desktop_task_service.go index dc611b1..3066791 100644 --- a/server/internal/tenant/app/desktop_task_service.go +++ b/server/internal/tenant/app/desktop_task_service.go @@ -199,6 +199,7 @@ func (s *DesktopTaskService) Complete(ctx context.Context, client *repository.De return nil, response.ErrUnauthorized(40108, "desktop_client_missing", "desktop client context is required") } + finalStatus := normalizeDesktopTaskTerminalStatus(req.Status) resultJSON, err := marshalOptionalJSON(req.Payload) if err != nil { return nil, response.ErrBadRequest(40086, "invalid_desktop_task_payload", "payload must be serializable") @@ -218,7 +219,7 @@ func (s *DesktopTaskService) Complete(ctx context.Context, client *repository.De previousTask, _ := taskRepo.GetByDesktopID(ctx, desktopID, client.WorkspaceID) previousAttemptID := activeAttemptID(previousTask) - task, err := taskRepo.Complete(ctx, desktopID, HashDesktopClientToken(strings.TrimSpace(req.LeaseToken)), req.Status, resultJSON, errorJSON) + task, err := taskRepo.Complete(ctx, desktopID, HashDesktopClientToken(strings.TrimSpace(req.LeaseToken)), finalStatus, resultJSON, errorJSON) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, response.ErrConflict(40983, "desktop_task_lease_invalid", "desktop task lease token is invalid or expired") @@ -230,7 +231,7 @@ func (s *DesktopTaskService) Complete(ctx context.Context, client *repository.De if finishErr := taskRepo.FinishAttempt(ctx, repository.FinishDesktopTaskAttemptParams{ TaskID: task.DesktopID, AttemptID: *previousAttemptID, - FinalStatus: &req.Status, + FinalStatus: &finalStatus, Error: errorJSON, }); finishErr != nil { s.logWarn("desktop task attempt finish failed after complete", finishErr, zap.String("task_id", task.DesktopID.String())) @@ -614,7 +615,7 @@ func scanDesktopTaskRows(rows pgx.Rows) ([]DesktopTaskView, error) { Platform: platform, Kind: kind, Payload: json.RawMessage(payload), - Status: status, + Status: normalizeDesktopTaskTerminalStatus(status), DedupKey: dedupKeyText, ActiveAttemptID: activeAttemptIDText, LeaseExpiresAt: leaseExpiresAtValue, @@ -672,7 +673,7 @@ func buildDesktopTaskView(task *repository.DesktopTask) DesktopTaskView { Platform: task.Platform, Kind: task.Kind, Payload: json.RawMessage(task.Payload), - Status: task.Status, + Status: normalizeDesktopTaskTerminalStatus(task.Status), DedupKey: task.DedupKey, ActiveAttemptID: activeAttemptID, LeaseExpiresAt: task.LeaseExpiresAt, @@ -702,6 +703,15 @@ func literalStringPtr(value string) *string { return &value } +func normalizeDesktopTaskTerminalStatus(status string) string { + switch strings.TrimSpace(status) { + case "unknown": + return "failed" + default: + return strings.TrimSpace(status) + } +} + func activeAttemptID(task *repository.DesktopTask) *uuid.UUID { if task == nil || task.ActiveAttemptID == nil { return nil diff --git a/server/internal/tenant/app/media_service.go b/server/internal/tenant/app/media_service.go index 9c209cc..c517888 100644 --- a/server/internal/tenant/app/media_service.go +++ b/server/internal/tenant/app/media_service.go @@ -1013,15 +1013,9 @@ func aggregatePublishStatuses(statuses []string) string { if counts["success"] == len(statuses) { return "success" } - if counts["pending_review"] == len(statuses) { - return "pending_review" - } - if counts["failed"] > 0 && (counts["success"] > 0 || counts["pending_review"] > 0) { + if counts["failed"] > 0 && counts["success"] > 0 { return "partial_success" } - if counts["pending_review"] > 0 { - return "pending_review" - } if counts["success"] > 0 { return "success" } @@ -1036,10 +1030,8 @@ func normalizePublishStatus(status string) string { return "publishing" case "success", "published", "publish_success": return "success" - case "failed", "publish_failed": + case "failed", "publish_failed", "pending_review": return "failed" - case "pending_review": - return "pending_review" default: return "failed" } diff --git a/server/internal/tenant/app/publish_record_support.go b/server/internal/tenant/app/publish_record_support.go index e583740..126cbc5 100644 --- a/server/internal/tenant/app/publish_record_support.go +++ b/server/internal/tenant/app/publish_record_support.go @@ -220,7 +220,7 @@ func desktopTaskToPublishStatus(taskStatus string) string { case "in_progress": return "publishing" case "unknown": - return "pending_review" + return "failed" case "succeeded": return "success" case "failed", "aborted":