diff --git a/apps/admin-web/src/components/PublishArticleModal.vue b/apps/admin-web/src/components/PublishArticleModal.vue index 1ed2e25..da39a5a 100644 --- a/apps/admin-web/src/components/PublishArticleModal.vue +++ b/apps/admin-web/src/components/PublishArticleModal.vue @@ -33,7 +33,7 @@ import { tenantAccountsApi, } from '@/lib/api' import { coverUploadRequired, deriveCoverFileName } from '@/lib/cover-requirements' -import { formatError } from '@/lib/errors' +import { formatError, formatStoredErrorMessage } from '@/lib/errors' import { accountInitial, buildPublishAccountCards, @@ -876,7 +876,8 @@ async function handleEnterprisePublishSuccess( } else if (failed.length > 0) { notification.error({ message: '企业站发布失败', - description: failed[0]?.error_message || '请检查插件、栏目和 PBootCMS API 配置。', + description: + formatStoredErrorMessage(failed[0]?.error_message) || '请检查插件、栏目和 PBootCMS API 配置。', placement: 'topRight', duration: 6, }) @@ -1094,14 +1095,14 @@ function resolveEnterpriseSiteDisabledReason( return '该企业站点已禁用。' } if (connectionStatus === 'error') { - return site.last_error?.trim() || '该企业站点连接异常,请先测试连通性。' + return formatStoredErrorMessage(site.last_error) || '该企业站点连接异常,请先测试连通性。' } return '' } function enterpriseSiteConnectionStatus(site: EnterpriseSiteConnection): string { const status = String(site.status ?? '').trim() - const lastError = site.last_error?.trim() + const lastError = formatStoredErrorMessage(site.last_error) const publishError = enterpriseSiteLatestPublishError(site) if (status === 'error' && lastError && publishError && lastError === publishError) { return 'connected' @@ -1118,7 +1119,7 @@ function enterpriseSiteLatestPublishError(site: EnterpriseSiteConnection): strin if (status !== 'failed' && status !== 'publish_failed') { return '' } - return record.error_message?.trim() || '' + return formatStoredErrorMessage(record.error_message) } function cmsTypeLabel(cmsType: string): string { diff --git a/apps/admin-web/src/lib/api.ts b/apps/admin-web/src/lib/api.ts index 17b59ac..bd16121 100644 --- a/apps/admin-web/src/lib/api.ts +++ b/apps/admin-web/src/lib/api.ts @@ -85,6 +85,7 @@ import type { KolWorkspaceCard, ListCustomerSupplierMediaResourcesResponse, ListDesktopPublishTasksParams, + ListPublishRecordsParams, ListMediaSupplyOrdersParams, ListMediaSupplyOrdersResponse, ListMediaSupplyWalletLedgersParams, @@ -116,6 +117,7 @@ import type { PublishKolPromptRequest, PublishEnterpriseSiteArticleRequest, PublishRecord, + PublishRecordListResponse, Question, QuestionCandidateResult, QuestionCombinationFillRequest, @@ -345,6 +347,7 @@ const currentBrandScopedPathPatterns = [ /^\/api\/tenant\/schedules(?:\/|$)/, /^\/api\/tenant\/instant-tasks(?:\/|$)/, /^\/api\/tenant\/publish-jobs(?:\/|$)/, + /^\/api\/tenant\/publish-records(?:\/|$)/, /^\/api\/tenant\/publish-tasks\/[^/]+\/retry(?:\/|$)/, /^\/api\/tenant\/enterprise-sites\/[^/]+\/publish(?:\/|$)/, /^\/api\/tenant\/media-supply\/orders(?:\/|$)/, @@ -1386,6 +1389,14 @@ export const publishTasksApi = { }, } +export const publishRecordsApi = { + list(params: ListPublishRecordsParams = {}) { + return apiClient.get('/api/tenant/publish-records', { + params, + }) + }, +} + export const enterpriseSitesApi = { list() { return apiClient.get('/api/tenant/enterprise-sites') diff --git a/apps/admin-web/src/lib/errors.ts b/apps/admin-web/src/lib/errors.ts index becbaa4..320c64f 100644 --- a/apps/admin-web/src/lib/errors.ts +++ b/apps/admin-web/src/lib/errors.ts @@ -78,6 +78,7 @@ const errorMessageMap: Record = { media_supply_wallet_user_not_found: '用户不存在或不属于当前租户', invalid_media_supply_wallet_user: '用户参数不正确', invalid_media_supply_wallet_delta: '调整金额不能为 0', + invalid_id: '参数 ID 不正确', media_supply_credentials_required: '媒体供应商账号未配置,请联系管理员处理', media_supply_session_required: '媒体供应商登录状态已失效,请联系管理员处理', media_supply_challenge_required: '媒体供应商登录验证失败,请联系管理员处理', @@ -145,6 +146,59 @@ const errorMessageMap: Record = { publisher_plugin_empty_response: '浏览器插件未返回数据,请刷新当前页面后重试', publisher_plugin_invalid_response: '浏览器插件返回了无效数据,请刷新当前页面后重试', publisher_plugin_unknown_error: '浏览器插件调用失败,请稍后重试', + enterprise_site_ping_failed: '企业站点连接测试失败', + enterprise_site_category_sync_failed: '企业站点栏目同步失败', + enterprise_site_publish_failed: '企业站点发布失败', + enterprise_site_disabled: '企业站点已禁用', + enterprise_site_category_required: '请选择企业站点栏目', + enterprise_site_categories_empty: 'CMS 未返回可发布栏目', + enterprise_site_already_exists: '该站点域名已被其他企业站点占用', + enterprise_site_create_failed: '企业站点创建失败', + enterprise_site_update_failed: '企业站点更新失败', + enterprise_site_name_required: '请输入站点名称', + enterprise_site_appid_required: '请输入 AppID', + enterprise_site_secret_required: '请输入 Secret', + invalid_enterprise_site_url: '站点域名格式不正确', + enterprise_site_cms_not_supported: '当前版本暂不支持该 CMS 类型', + enterprise_site_query_failed: '企业站点列表读取失败', + enterprise_site_scan_failed: '企业站点数据解析失败', + enterprise_site_delete_failed: '企业站点删除失败', + enterprise_site_not_found: '企业站点连接不存在或已删除', + enterprise_site_ping_update_failed: '企业站点连接状态更新失败', + enterprise_site_category_query_failed: '企业站点栏目读取失败', + enterprise_site_category_scan_failed: '企业站点栏目解析失败', + enterprise_site_category_clear_failed: '企业站点栏目同步失败', + enterprise_site_category_payload_failed: '企业站点栏目同步失败', + enterprise_site_category_insert_failed: '企业站点栏目保存失败', + enterprise_site_sync_update_failed: '企业站点同步状态更新失败', + enterprise_site_category_sync_begin_failed: '企业站点栏目同步失败', + enterprise_site_category_sync_commit_failed: '企业站点栏目同步失败', + enterprise_site_category_lookup_failed: '企业站点栏目校验失败', + enterprise_site_category_not_found: '企业站点栏目不存在,请先同步栏目', + enterprise_site_markdown_render_failed: '文章内容渲染失败', + enterprise_site_article_empty: '文章内容为空', + enterprise_site_publish_payload_failed: '企业站点发布记录创建失败', + enterprise_site_publish_response_failed: 'CMS 响应保存失败', + enterprise_site_publish_begin_failed: '企业站点发布记录创建失败', + enterprise_site_publish_batch_failed: '企业站点发布批次创建失败', + enterprise_site_publish_record_failed: '企业站点发布记录创建失败', + enterprise_site_article_publish_status_failed: '文章发布状态更新失败', + enterprise_site_publish_commit_failed: '企业站点发布记录提交失败', + enterprise_site_publish_ack_failed: '合规确认处理失败', + enterprise_site_publish_finish_begin_failed: '企业站点发布结果更新失败', + enterprise_site_publish_record_update_failed: '企业站点发布记录更新失败', + enterprise_site_publish_batch_update_failed: '企业站点发布批次更新失败', + enterprise_site_article_publish_update_failed: '文章发布状态更新失败', + enterprise_site_publish_connection_update_failed: '企业站点连接状态更新失败', + enterprise_site_publish_finish_commit_failed: '企业站点发布结果提交失败', + enterprise_site_article_lookup_failed: '文章读取失败', + enterprise_site_article_version_lookup_failed: '文章版本读取失败', + enterprise_site_article_version_reconstruct_failed: '文章版本内容恢复失败', + enterprise_site_article_version_snapshot_lookup_failed: '文章版本快照读取失败', + enterprise_site_lookup_failed: '企业站点连接读取失败', + enterprise_site_secret_encrypt_failed: '企业站点凭证保存失败', + enterprise_site_secret_decrypt_failed: '企业站点凭证读取失败', + enterprise_site_url_required: '请填写站点域名', installation_offline: '桌面客户端未在线,请打开桌面客户端后重试', network_error: '网络连接失败,请稍后重试', request_timeout: '请求超时,AI 生成耗时较长,请稍后重试或稍候再查看结果', @@ -195,6 +249,17 @@ const LEGACY_MEDIA_SUPPLY_REFRESH_PATTERN = /^failed to refresh supplier price(?: for \d+ selected resources?)?$/i const LEGACY_MEDIA_SUPPLY_LOCKED_COST_PATTERN = /^supplier cost increased above locked sell price/i +const LEGACY_CMS_CREDENTIAL_PATTERN = + /^CMS request failed; please verify site credentials and plugin installation$/i +const LEGACY_CMS_REQUEST_FAILED_PATTERN = /^CMS request failed$/i +const LEGACY_ENTERPRISE_SITE_PING_FAILED_PATTERN = /^enterprise site ping failed: /i +const LEGACY_ENTERPRISE_SITE_CATEGORY_SYNC_FAILED_PATTERN = + /^enterprise site category sync failed: /i +const LEGACY_PBOOTCMS_HTTP_PATTERN = /^pbootcms\s+\w+\s+returned http \d+$/i +const LEGACY_PBOOTCMS_CODE_PATTERN = /^pbootcms\s+\w+\s+failed with code \d+$/i +const LEGACY_ENTERPRISE_SITE_FAILED_DETAIL_PATTERN = /^failed to .*enterprise site/i +const LEGACY_ENTERPRISE_SITE_EXISTS_PATTERN = + /^(enterprise site connection already exists|another enterprise site already uses this domain)$/i function translateRawErrorMessage(raw: string): string | null { const normalized = raw.trim() @@ -211,7 +276,50 @@ function translateRawErrorMessage(raw: string): string | null { if (LEGACY_MEDIA_SUPPLY_LOCKED_COST_PATTERN.test(normalized)) { return '媒体成本价已变化,当前锁定价格低于成本,请重新提交' } + if (LEGACY_CMS_CREDENTIAL_PATTERN.test(normalized)) { + return 'CMS 请求失败,请检查站点凭证和接口安装状态' + } + if (LEGACY_CMS_REQUEST_FAILED_PATTERN.test(normalized)) { + return 'CMS 请求失败' + } + if (LEGACY_ENTERPRISE_SITE_PING_FAILED_PATTERN.test(normalized)) { + return `企业站点连接测试失败:${formatStoredErrorMessage( + normalized.replace(LEGACY_ENTERPRISE_SITE_PING_FAILED_PATTERN, ''), + )}` + } + if (LEGACY_ENTERPRISE_SITE_CATEGORY_SYNC_FAILED_PATTERN.test(normalized)) { + return `企业站点栏目同步失败:${formatStoredErrorMessage( + normalized.replace(LEGACY_ENTERPRISE_SITE_CATEGORY_SYNC_FAILED_PATTERN, ''), + )}` + } const lower = normalized.toLowerCase() + if (lower.startsWith('request pbootcms ')) { + return 'CMS 请求失败,请检查站点地址和网络连通性' + } + if (lower.startsWith('parse pbootcms response')) { + return 'CMS 响应解析失败,请确认接口返回 JSON 格式' + } + if (lower.startsWith('parse pbootcms data')) { + return 'CMS 数据解析失败,请确认接口返回数据格式' + } + if (lower.startsWith('invalid pbootcms site url')) { + return '站点域名格式不正确' + } + if (LEGACY_PBOOTCMS_HTTP_PATTERN.test(normalized)) { + return 'CMS 接口请求失败,请检查站点接口是否可访问' + } + if (LEGACY_PBOOTCMS_CODE_PATTERN.test(normalized)) { + return 'CMS 接口返回失败,请检查站点接口配置' + } + if (lower.startsWith('pbootcms ') && lower.includes(' endpoint is unavailable')) { + return 'CMS 接口不可用,请确认接口安装路径' + } + if (LEGACY_ENTERPRISE_SITE_FAILED_DETAIL_PATTERN.test(normalized)) { + return '企业站点操作失败,请稍后重试' + } + if (LEGACY_ENTERPRISE_SITE_EXISTS_PATTERN.test(normalized)) { + return errorMessageMap.enterprise_site_already_exists + } const hiddenSupplierToken = ['mei', 'jie', 'quan'].join('') if (lower.includes(hiddenSupplierToken)) { return '媒体供应商请求失败,请稍后重试或联系管理员' @@ -247,7 +355,11 @@ export function formatError(error: unknown): string { if (isAiPointsInsufficient(error)) { return message } - return error.detail ? `${message}: ${error.detail}` : message + const detail = error.detail ? formatStoredErrorMessage(error.detail) : '' + if (detail === message) { + return message + } + return detail ? `${message}:${detail}` : message } if (error instanceof Error) { diff --git a/apps/admin-web/src/views/MediaView.vue b/apps/admin-web/src/views/MediaView.vue index f99f5e8..b2d6479 100644 --- a/apps/admin-web/src/views/MediaView.vue +++ b/apps/admin-web/src/views/MediaView.vue @@ -1,16 +1,18 @@