feat(desktop): drop parked review flow, add publish management
SaaS 侧人工审核后才创建 publish job,desktop 不再做二次审核:移除 manual/waiting_user/parked/from_parked 状态机与 LeaseFromParked 查询, desktop client 只执行发布并新增"发布管理"页(待发布队列 / 历史 / 再次 发送)。同时抽离 @geo/publisher-platforms 共享适配器包、新增 Redis-based desktop_presence 与 publish_record_support,刷新 admin-web 发布弹窗与 媒体库;plan A / spec 文档同步口径。
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||
<path
|
||||
d="M0 0m170.666667 0l682.666666 0q170.666667 0 170.666667 170.666667l0 682.666666q0 170.666667-170.666667 170.666667l-682.666666 0q-170.666667 0-170.666667-170.666667l0-682.666666q0-170.666667 170.666667-170.666667Z"
|
||||
fill="#FECC32"></path>
|
||||
<path
|
||||
d="M512 199.369697c-202.364121 0-366.390303 164.026182-366.390303 366.390303A365.164606 365.164606 0 0 0 250.414545 822.30303l99.669334-92.004848a230.089697 230.089697 0 0 1-68.949334-164.553697c0-127.503515 103.330909-230.865455 230.84994-230.865455s230.865455 103.361939 230.865454 230.865455c0 65.318788-27.10497 124.276364-70.68703 166.275879l99.126303 92.609939a365.288727 365.288727 0 0 0 107.116606-258.885818c0-202.364121-164.057212-366.405818-366.421333-366.405818z"
|
||||
fill="#000000"></path>
|
||||
<path
|
||||
d="M279.489939 522.627879c37.701818-13.187879 137.75903-32.845576 248.754425-30.409697 110.995394 2.435879 150.900364 12.955152 210.788848 26.298182 30.937212 6.888727 77.746424 42.713212 80.32194 83.673212 2.435879 38.291394-66.746182 57.607758-95.232 57.607757 0 0-105.580606-27.415273-211.999031-27.492848-106.433939-0.077576-213.767758 27.182545-213.767757 27.182545l-69.725091-50.641454 50.858666-86.217697z"
|
||||
fill="#000000"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" rx="8" />
|
||||
<text x="30" y="320" font-family="Source Han Sans SC" font-size="220" fill="#D95354" font-weight="bold">简书</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 256 B |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="128" height="128">
|
||||
<path
|
||||
d="M0 0m225.890154 0l572.219692 0q225.890154 0 225.890154 225.890154l0 572.219692q0 225.890154-225.890154 225.890154l-572.219692 0q-225.890154 0-225.890154-225.890154l0-572.219692q0-225.890154 225.890154-225.890154Z"
|
||||
fill="#EE3731"></path>
|
||||
<path
|
||||
d="M280.725047 864.454516l-56.635994-328.614599a610.844676 610.844676 0 0 1-51.282037 67.491947l-8.104955-3.367813q23.438987-75.251485 39.278808-176.668281 15.469731-106.338983 15.790476-204.474321l137.401809 15.691785c-1.060923 9.782693-9.264568 16.098883-24.425892 18.627826q-16.54299 83.714193-43.806233 154.783669l32.074403 5.464985c-0.629152 7.056369-6.834315 12.644717-18.504463 16.962425l67.491946 391.147349c-0.209717 4.095655-7.574494 10.942306-22.007975 20.317901-16.629345 10.214464-33.308034 16.839062-49.801679 19.738094z m133.096438-25.696532l-81.18525-470.210753 111.026781-8.746443c0.222054 9.585312-6.106473 16.641681-19.158287 21.267797l66.44336 384.89284 225.421374-38.859373 34.220921-66.01159a177.901912 177.901912 0 0 1-35.05979 9.474286L698.827231 673.562471l-4.416398-25.474478-94.545472 16.320936 2.948378 16.740372c0.629152 3.996964-5.057887 9.572976-17.172143 16.740371-14.630862 8.52439-32.32113 14.519836-52.848747 18.109702l-16.740371 2.837351L447.721662 323.211295l78.545279 14.803571-9.165877-57.067765L356.75372 308.407725l-8.536726-17.788958 165.627284-28.644909-15.691785-91.17766 129.296855-11.26305c-0.320744 12.003229-7.895238 20.971725-23.056562 26.745118 0.629152 11.361741 1.468021 31.272543 2.738661 59.584372l28.102112-4.835833 37.79845-73.499729 36.540148 19.984821q41.178599 22.637127 65.283747 37.79845c-0.641488 7.475803-6.427217 12.212946-17.591577 14.100401l-149.614755 25.733541c0.629152 22.42741 1.233631 44.965846 1.998482 67.393256l26.745118-4.638452 36.219403-58.745504 92.028865 49.123183c-4.107991 8.104955-12.53369 14.322455-25.06738 18.84988l51.393063 297.749154c0.209717 1.36933-2.109509 4.107991-6.957678 8.104955A107.399906 107.399906 0 0 1 762.852675 656.291638q60.953703 33.073644 96.445264 54.440132c-0.530461 7.475803-6.427217 12.212946-17.591577 14.112737l-347.452142 60.003807 2.208199 12.743407c0.740179 4.428735-6.106473 11.16436-20.737336 20.108184s-30.643392 15.691785-45.903405 18.319419zM561.955883 444.35385l94.545472-16.283928-14.100401-81.814401-94.668835 16.283927z m16.851398 97.913284l94.545472-16.320937-13.56994-79.174431-94.656498 16.320937z m17.788958 103.291915l94.656499-16.320937-14.630863-84.553061-94.557808 16.320936z"
|
||||
fill="#FFFFFF"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="128" height="128">
|
||||
<path
|
||||
d="M849.92 51.2H174.08c-67.8656 0-122.88 55.0144-122.88 122.88v675.84c0 67.8656 55.0144 122.88 122.88 122.88h675.84c67.8656 0 122.88-55.0144 122.88-122.88V174.08c0-67.8656-55.0144-122.88-122.88-122.88zM448.18432 230.94272c176.98304-53.95968 267.17696 110.98624 267.17696 110.98624-32.59392-17.78176-130.39104-37.53472-235.09504 16.7936s-126.4384 172.87168-126.4384 172.87168c-42.56256-45.4144-44.4928-118.6304-44.4928-118.6304 5.03296-137.41568 138.84928-182.02112 138.84928-182.02112zM393.50784 796.42112c-256.12288-49.6384-197.85216-273.38752-133.81632-371.95264 0 0-2.88256 138.13248 130.22208 214.4 0 0 15.82592 7.1936 10.79296 30.21312l-5.03808 29.49632s-6.656 20.1472 6.02624 22.30272c0 0 4.04992 0 13.39904-6.4768l48.92672-32.37376s10.07104-7.1936 23.01952-5.03808c12.94848 2.16064 95.68768 23.74656 177.70496-44.60032-0.00512 0-15.10912 213.67296-271.23712 164.02944z m256.8448-19.42016c16.54784-7.9104 97.1264-102.8864 58.98752-231.66464s-167.6288-157.55776-167.6288-157.55776c66.19136-28.0576 143.89248-7.19872 143.89248-7.19872 117.9904 34.5344 131.6608 146.77504 131.6608 146.77504 23.01952 200.71936-166.912 249.64608-166.912 249.64608z"
|
||||
fill="#01CC7A"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { QuestionCircleOutlined, UserOutlined } from "@ant-design/icons-vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import type { MediaPlatform, PlatformAccount } from "@geo/shared-types";
|
||||
import type { MediaPlatform } from "@geo/shared-types";
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import { articlesApi, mediaApi, resolveApiURL } from "@/lib/api";
|
||||
import { articlesApi, mediaApi } from "@/lib/api";
|
||||
import { getPublishStatusMeta } from "@/lib/display";
|
||||
import { getPublishPlatformMeta, normalizePublishPlatformId } from "@/lib/publish-platforms";
|
||||
|
||||
@@ -49,13 +49,6 @@ const platformsQuery = useQuery({
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
const accountsQuery = useQuery({
|
||||
queryKey: ["media", "platform-accounts", "publish-status"],
|
||||
enabled: computed(() => popoverOpen.value && shouldLoadPlatforms.value),
|
||||
queryFn: () => mediaApi.accounts(),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
const publishRecordsQuery = useQuery({
|
||||
queryKey: computed(() => ["articles", "publish-records", props.articleId, "publish-status"]),
|
||||
enabled: computed(() => popoverOpen.value && shouldLoadPlatforms.value),
|
||||
@@ -73,12 +66,6 @@ const platformMap = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const accountMap = computed(() => {
|
||||
return new Map(
|
||||
(accountsQuery.data.value ?? []).map((account: PlatformAccount) => [account.id, account]),
|
||||
);
|
||||
});
|
||||
|
||||
const platformEntries = computed<PublishPlatformEntry[]>(() => {
|
||||
const seen = new Set<string>();
|
||||
const entries: PublishPlatformEntry[] = [];
|
||||
@@ -92,7 +79,6 @@ const platformEntries = computed<PublishPlatformEntry[]>(() => {
|
||||
|
||||
const normalizedId = normalizePublishPlatformId(record.platform_id);
|
||||
const platform = platformMap.value.get(normalizedId);
|
||||
const account = accountMap.value.get(record.platform_account_id);
|
||||
const fallback = getPublishPlatformMeta(normalizedId);
|
||||
|
||||
entries.push({
|
||||
@@ -100,8 +86,8 @@ const platformEntries = computed<PublishPlatformEntry[]>(() => {
|
||||
name: record.platform_name || platform?.name || fallback.name,
|
||||
shortName: platform?.short_name || fallback.shortName,
|
||||
accent: platform?.accent_color || fallback.accent,
|
||||
accountName: account?.nickname || record.platform_nickname || "--",
|
||||
avatarUrl: resolveApiURL(account?.avatar_url),
|
||||
accountName: record.platform_nickname || "--",
|
||||
avatarUrl: "",
|
||||
status: normalizePublishRecordStatus(record.status),
|
||||
});
|
||||
}
|
||||
@@ -251,7 +237,7 @@ function getAccountInitial(accountName: string): string {
|
||||
<template #content>
|
||||
<div class="article-publish-status__popover">
|
||||
<div
|
||||
v-if="publishRecordsQuery.isPending.value || platformsQuery.isPending.value || accountsQuery.isPending.value"
|
||||
v-if="publishRecordsQuery.isPending.value || platformsQuery.isPending.value"
|
||||
class="article-publish-status__loading"
|
||||
>
|
||||
<a-spin size="small" />
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useI18n } from "vue-i18n";
|
||||
|
||||
import PromptRuleModal from "@/components/PromptRuleModal.vue";
|
||||
import PublishPlatformSelector from "@/components/PublishPlatformSelector.vue";
|
||||
import { generateApi, mediaApi, promptRulesApi, schedulesApi } from "@/lib/api";
|
||||
import { generateApi, mediaApi, promptRulesApi, schedulesApi, tenantAccountsApi } from "@/lib/api";
|
||||
import { formatError } from "@/lib/errors";
|
||||
import { IMAGE_UPLOAD_ACCEPT, useImageUploadProgress, validateImageUploadFile } from "@/lib/image-webp";
|
||||
import {
|
||||
@@ -55,9 +55,9 @@ const platformsQuery = useQuery({
|
||||
});
|
||||
|
||||
const accountsQuery = useQuery({
|
||||
queryKey: ["media", "platform-accounts", "task-drawer"],
|
||||
queryKey: ["tenant", "desktop-accounts", "task-drawer"],
|
||||
enabled: computed(() => props.open),
|
||||
queryFn: () => mediaApi.accounts(),
|
||||
queryFn: () => tenantAccountsApi.list(),
|
||||
});
|
||||
|
||||
const allPlatforms = computed(() =>
|
||||
|
||||
@@ -152,7 +152,7 @@ const enUS = {
|
||||
},
|
||||
media: {
|
||||
title: "Media Management",
|
||||
description: "Manage media bindings, local extension state, and one-click publishing from a single control plane.",
|
||||
description: "Review desktop-managed media bindings, authorization state, and sync results in one place.",
|
||||
},
|
||||
brands: {
|
||||
title: "Brand Library",
|
||||
|
||||
@@ -152,7 +152,7 @@ const zhCN = {
|
||||
},
|
||||
media: {
|
||||
title: "媒体管理",
|
||||
description: "统一管理媒体账号绑定、本地插件状态和一键发布执行链路。",
|
||||
description: "统一查看桌面端媒体账号绑定、授权状态与同步结果。",
|
||||
},
|
||||
brands: {
|
||||
title: "品牌词库",
|
||||
|
||||
@@ -15,6 +15,8 @@ import type {
|
||||
CompetitorRequest,
|
||||
CreatePluginSessionRequest,
|
||||
CreatePluginSessionResponse,
|
||||
CreatePublishJobRequest,
|
||||
CreatePublishJobResponse,
|
||||
CreatePublishBatchRequest,
|
||||
CreatePublishBatchResponse,
|
||||
GenerateFromRuleRequest,
|
||||
@@ -104,6 +106,7 @@ import type {
|
||||
CreateKolPromptRequest,
|
||||
UpdateKolPackageRequest,
|
||||
UpdateKolPromptRequest,
|
||||
DesktopAccountInfo,
|
||||
UserInfo,
|
||||
WorkspaceOverview,
|
||||
} from "@geo/shared-types";
|
||||
@@ -869,7 +872,7 @@ export const brandsApi = {
|
||||
|
||||
export const mediaApi = {
|
||||
platforms() {
|
||||
return removedMediaApi<MediaPlatform[]>("platforms");
|
||||
return apiClient.get<MediaPlatform[]>("/api/tenant/media/platforms");
|
||||
},
|
||||
accounts() {
|
||||
return removedMediaApi<PlatformAccount[]>("accounts");
|
||||
@@ -888,6 +891,22 @@ export const mediaApi = {
|
||||
},
|
||||
};
|
||||
|
||||
export const tenantAccountsApi = {
|
||||
list() {
|
||||
return apiClient.get<DesktopAccountInfo[]>("/api/tenant/accounts");
|
||||
},
|
||||
requestDelete(id: string, options?: { undo?: boolean }) {
|
||||
const query = options?.undo ? "?undo=true" : "";
|
||||
return apiClient.post<DesktopAccountInfo>(`/api/tenant/accounts/${encodeURIComponent(id)}/request-delete${query}`);
|
||||
},
|
||||
};
|
||||
|
||||
export const publishJobsApi = {
|
||||
create(payload: CreatePublishJobRequest) {
|
||||
return apiClient.post<CreatePublishJobResponse, CreatePublishJobRequest>("/api/tenant/publish-jobs", payload);
|
||||
},
|
||||
};
|
||||
|
||||
function removedMediaApi<T>(action: string): Promise<T> {
|
||||
return Promise.reject(new ApiClientError({
|
||||
message: "desktop_media_api_removed",
|
||||
|
||||
@@ -50,6 +50,10 @@ const errorMessageMap: Record<string, string> = {
|
||||
brand_not_found: "品牌不存在或已删除",
|
||||
keyword_not_found: "关键词不存在或已删除",
|
||||
publish_cover_required: "已选择百家号,请先上传封面图",
|
||||
desktop_account_client_missing: "所选账号还没有绑定桌面客户端,暂时无法发布",
|
||||
desktop_account_not_found: "所选桌面账号不存在,请刷新后重试",
|
||||
desktop_publish_job_store_unavailable: "发布队列暂时不可用,请稍后重试",
|
||||
desktop_media_api_removed: "旧版媒体接口已下线,请刷新页面后重试",
|
||||
publisher_plugin_timeout: "浏览器插件响应超时,请刷新当前页面后重试",
|
||||
publisher_plugin_empty_response: "浏览器插件未返回数据,请刷新当前页面后重试",
|
||||
publisher_plugin_invalid_response: "浏览器插件返回了无效数据,请刷新当前页面后重试",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MediaPlatform, PlatformAccount } from "@geo/shared-types";
|
||||
import type { DesktopAccountInfo, MediaPlatform, PlatformAccount } from "@geo/shared-types";
|
||||
|
||||
export interface PublishPlatformOption {
|
||||
id: string;
|
||||
@@ -53,10 +53,14 @@ export function normalizePublishPlatformIds(platformIds?: Array<string | null |
|
||||
|
||||
export function buildPublishPlatformOptions(
|
||||
platforms: MediaPlatform[],
|
||||
accounts?: PlatformAccount[] | null,
|
||||
accounts?: Array<PlatformAccount | DesktopAccountInfo> | null,
|
||||
): PublishPlatformOption[] {
|
||||
const boundPlatformIds = new Set(
|
||||
(accounts ?? []).map((account) => normalizePublishPlatformId(account.platform_id)).filter(Boolean),
|
||||
(accounts ?? [])
|
||||
.map((account) =>
|
||||
normalizePublishPlatformId("platform_id" in account ? account.platform_id : account.platform),
|
||||
)
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
return platforms.map((platform) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import ArticleEditorCanvas from "@/components/ArticleEditorCanvas.vue";
|
||||
import CoverPickerModal from "@/components/CoverPickerModal.vue";
|
||||
import PublishArticleModal from "@/components/PublishArticleModal.vue";
|
||||
import PublishPlatformSelector from "@/components/PublishPlatformSelector.vue";
|
||||
import { articlesApi, mediaApi, resolveApiURL } from "@/lib/api";
|
||||
import { articlesApi, mediaApi, resolveApiURL, tenantAccountsApi } from "@/lib/api";
|
||||
import {
|
||||
coverUploadRequired,
|
||||
deriveCoverFileName,
|
||||
@@ -55,8 +55,8 @@ const platformsQuery = useQuery({
|
||||
});
|
||||
|
||||
const accountsQuery = useQuery({
|
||||
queryKey: ["media", "platform-accounts", "article-editor"],
|
||||
queryFn: () => mediaApi.accounts(),
|
||||
queryKey: ["tenant", "desktop-accounts", "article-editor"],
|
||||
queryFn: () => tenantAccountsApi.list(),
|
||||
});
|
||||
|
||||
const publishPlatformOptions = computed(() =>
|
||||
|
||||