diff --git a/apps/admin-web/src/components/PublishArticleModal.vue b/apps/admin-web/src/components/PublishArticleModal.vue index 22c31de..93cfd68 100644 --- a/apps/admin-web/src/components/PublishArticleModal.vue +++ b/apps/admin-web/src/components/PublishArticleModal.vue @@ -9,7 +9,7 @@ import { useI18n } from "vue-i18n"; import CoverPickerModal from "@/components/CoverPickerModal.vue"; import { articlesApi, mediaApi, publishJobsApi, resolveApiURL, tenantAccountsApi } from "@/lib/api"; import { coverUploadRequired, deriveCoverFileName } from "@/lib/cover-requirements"; -import { formatDateTime } from "@/lib/display"; +import { resolveAccountCheckedAt, resolveAccountHealth } from "@/lib/desktop-account-runtime"; import { formatError } from "@/lib/errors"; import { getPublishPlatformMeta, @@ -20,16 +20,34 @@ import { type PublishState = "immediate" | "queued" | "unavailable"; +const platformLogoCatalog: Record = { + toutiaohao: "/logos/logo_toutiao.png", + baijiahao: "/logos/logo_baijiahao.png", + sohuhao: "/logos/logo_souhu.png", + qiehao: "/logos/logo_qiehao.png", + zhihu: "/logos/logo_zhihu.png", + wangyihao: "/logos/logo_wangyihao.png", + jianshu: "/logos/logo_jianshu.svg", + bilibili: "/logos/logo_bilibili.png", + juejin: "/logos/logo_juejin.png", + smzdm: "/logos/logo_smzdm.svg", + weixin_gzh: "/logos/logo_weixin_gzh.svg", + zol: "/logos/logo_zol.png", + dongchedi: "/logos/logo_dongchedi.svg", +}; + interface PublishAccountCard { id: string; platformId: string; platformName: string; platformShortName: string; platformAccent: string; + platformLogoUrl: string | null; displayName: string; platformUid: string; avatarUrl: string | null; health: DesktopAccountInfo["health"]; + verifiedAt: string | null; clientId: string | null; clientOnline: boolean | null; clientDeviceName: string | null; @@ -105,7 +123,7 @@ watch( { immediate: true }, ); -const platformMap = computed(() => { +const platformMap = computed>(() => { return new Map( (platformsQuery.data.value ?? []).map((platform: MediaPlatform) => [ normalizePublishPlatformId(platform.platform_id), @@ -125,10 +143,6 @@ const publishAccounts = computed(() => { }); }); -const rawAccountMap = computed(() => { - return new Map(publishAccounts.value.map((account) => [account.id, account])); -}); - const articlePlatformIds = computed(() => normalizePublishPlatformIds(detailQuery.data.value?.platforms ?? [])); const accountCards = computed(() => { @@ -140,6 +154,7 @@ const accountCards = computed(() => { const platform = platformMap.value.get(platformId); const fallback = getPublishPlatformMeta(platformId); const publishState = resolvePublishState(account); + const health = resolveAccountHealth(account); return { id: account.id, @@ -147,10 +162,12 @@ const accountCards = computed(() => { platformName: platform?.name || fallback.name, platformShortName: platform?.short_name || fallback.shortName, platformAccent: platform?.accent_color || fallback.accent, + platformLogoUrl: resolvePlatformLogoUrl(platformId, platform?.logo_url), displayName: account.display_name, platformUid: normalizePlatformUid(account.platform_uid), avatarUrl: resolveApiURL(account.avatar_url), - health: account.health, + health, + verifiedAt: resolveAccountCheckedAt(account), clientId: account.client_id, clientOnline: account.client_online, clientDeviceName: account.client_device_name, @@ -172,8 +189,8 @@ const accountCards = computed(() => { return publishGap; } - const leftVerifiedAt = Date.parse(rawAccountMap.value.get(left.id)?.verified_at ?? "") || 0; - const rightVerifiedAt = Date.parse(rawAccountMap.value.get(right.id)?.verified_at ?? "") || 0; + const leftVerifiedAt = Date.parse(left.verifiedAt ?? "") || 0; + const rightVerifiedAt = Date.parse(right.verifiedAt ?? "") || 0; return rightVerifiedAt - leftVerifiedAt; }); }); @@ -375,7 +392,7 @@ function handleRemoveCover(): void { } function resolvePublishState(account: DesktopAccountInfo): PublishState { - if (account.health !== "live") { + if (resolveAccountHealth(account) !== "live") { return "unavailable"; } if (!account.client_id) { @@ -391,7 +408,7 @@ function resolveStatusText(account: DesktopAccountInfo, publishState: PublishSta if (publishState === "queued") { return "客户端当前离线,任务会先落库排队,等桌面端上线后自动继续发布。"; } - if (account.health !== "live") { + if (resolveAccountHealth(account) !== "live") { return "该账号授权状态异常,请先在桌面端重新校验或重新授权。"; } return "该账号还没有绑定桌面客户端,当前不能进入发布队列。"; @@ -464,6 +481,15 @@ function accountInitial(card: PublishAccountCard): string { return trimmed ? trimmed.slice(0, 1).toUpperCase() : card.platformShortName; } +function resolvePlatformLogoUrl(platformId: string, remoteLogoUrl?: string | null): string | null { + const localLogoUrl = platformLogoCatalog[platformId]; + if (localLogoUrl) { + return localLogoUrl; + } + const resolvedRemoteLogoUrl = resolveApiURL(remoteLogoUrl); + return resolvedRemoteLogoUrl || null; +} + function normalizePlatformUid(value?: string | null): string { let normalized = String(value ?? "").trim(); while (normalized.toLowerCase().startsWith("platform:")) { @@ -538,7 +564,21 @@ function normalizePlatformUid(value?: string | null): string {
{{ account.displayName }} - {{ account.platformName }} · {{ account.platformUid }} + + + + {{ account.platformName }} · {{ account.platformUid }} +
@@ -898,6 +938,39 @@ function normalizePlatformUid(value?: string | null): string { text-overflow: ellipsis; } +.publish-modal__platform-line { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +.publish-modal__platform-logo { + width: 14px; + height: 14px; + border-radius: 3px; + object-fit: contain; + flex-shrink: 0; + background: #fff; +} + +.publish-modal__platform-logo--fallback { + display: inline-flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 9px; + font-weight: 600; + line-height: 1; + margin-top: 0; +} + +.publish-modal__platform-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} + .publish-modal__state-pill { display: inline-flex; align-items: center; diff --git a/apps/admin-web/src/lib/desktop-account-runtime.ts b/apps/admin-web/src/lib/desktop-account-runtime.ts new file mode 100644 index 0000000..69449c6 --- /dev/null +++ b/apps/admin-web/src/lib/desktop-account-runtime.ts @@ -0,0 +1,9 @@ +import type { DesktopAccountInfo } from "@geo/shared-types"; + +export function resolveAccountHealth(account: DesktopAccountInfo): DesktopAccountInfo["health"] { + return account.runtime_health ?? account.health; +} + +export function resolveAccountCheckedAt(account: DesktopAccountInfo): string | null { + return account.runtime_checked_at ?? account.runtime_verified_at ?? account.verified_at; +} diff --git a/apps/admin-web/src/views/MediaView.vue b/apps/admin-web/src/views/MediaView.vue index c99b389..4e1d4b8 100644 --- a/apps/admin-web/src/views/MediaView.vue +++ b/apps/admin-web/src/views/MediaView.vue @@ -6,6 +6,7 @@ import { computed, ref } from "vue"; import { useI18n } from "vue-i18n"; import { mediaApi, resolveApiURL, tenantAccountsApi } from "@/lib/api"; +import { resolveAccountCheckedAt, resolveAccountHealth } from "@/lib/desktop-account-runtime"; import { formatDateTime } from "@/lib/display"; import { getPublishPlatformMeta, isPublishPlatformId, normalizePublishPlatformId } from "@/lib/publish-platforms"; @@ -85,6 +86,7 @@ const mediaAccounts = computed(() => { const platform = platformMap.value.get(platformId); const fallback = getPublishPlatformMeta(platformId); const publishState = resolvePublishState(account); + const health = resolveAccountHealth(account); return { id: account.id, @@ -96,8 +98,8 @@ const mediaAccounts = computed(() => { displayName: account.display_name, platformUid: normalizePlatformUid(account.platform_uid), avatarUrl: resolveApiURL(account.avatar_url), - health: account.health, - verifiedAt: account.verified_at, + health, + verifiedAt: resolveAccountCheckedAt(account), clientId: account.client_id, clientOnline: account.client_online, clientDeviceName: account.client_device_name, @@ -149,7 +151,7 @@ const filteredAccounts = computed(() => { }); function resolvePublishState(account: DesktopAccountInfo): PublishState { - if (account.health !== "live") { + if (resolveAccountHealth(account) !== "live") { return "unavailable"; } if (!account.client_id) { @@ -165,7 +167,7 @@ function resolvePublishHint(account: DesktopAccountInfo, publishState: PublishSt if (publishState === "queued") { return "客户端当前离线,任务会先进入发布队列,等桌面端上线后自动继续。"; } - if (account.health !== "live") { + if (resolveAccountHealth(account) !== "live") { return "授权状态异常,先在桌面端重新校验后再发布。"; } return "还没有绑定桌面客户端,当前无法加入发布队列。"; diff --git a/apps/admin-web/src/views/TrackingView.vue b/apps/admin-web/src/views/TrackingView.vue index f005128..3bfb346 100644 --- a/apps/admin-web/src/views/TrackingView.vue +++ b/apps/admin-web/src/views/TrackingView.vue @@ -19,6 +19,7 @@ import { useRoute, useRouter } from "vue-router"; import PageHero from "@/components/PageHero.vue"; import { brandsApi, monitoringApi, tenantAccountsApi } from "@/lib/api"; +import { resolveAccountCheckedAt, resolveAccountHealth } from "@/lib/desktop-account-runtime"; import { formatDateTime } from "@/lib/display"; import { formatError } from "@/lib/errors"; import { isMonitoringPlatformId, normalizeMonitoringPlatformFilter, normalizeMonitoringPlatformId } from "@/lib/monitoring-platforms"; @@ -354,8 +355,8 @@ const aiPlatformStatusCards = computed(() => { return aiPlatformCatalog.map((platform) => { const matchedAccounts = [...(accountGroups.get(platform.id) ?? [])].sort((left, right) => { - const leftAt = Date.parse(left.verified_at ?? "") || 0; - const rightAt = Date.parse(right.verified_at ?? "") || 0; + const leftAt = Date.parse(resolveAccountCheckedAt(left) ?? "") || 0; + const rightAt = Date.parse(resolveAccountCheckedAt(right) ?? "") || 0; return rightAt - leftAt; }); const breakdown = breakdownMap.get(platform.id); @@ -537,7 +538,7 @@ function accountAuthLabel(account: DesktopAccountInfo | null): string { return "未绑定"; } - switch (account.health) { + switch (resolveAccountHealth(account)) { case "live": return "授权正常"; case "captcha": @@ -554,7 +555,7 @@ function accountAuthColor(account: DesktopAccountInfo | null): string { return "default"; } - switch (account.health) { + switch (resolveAccountHealth(account)) { case "live": return "green"; case "captcha":