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 文档同步口径。
This commit is contained in:
2026-04-20 09:52:48 +08:00
parent b16e9f0bd1
commit a617d39a4a
93 changed files with 5519 additions and 3594 deletions
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { SearchOutlined, ReloadOutlined, ApiOutlined, ArrowRightOutlined, LinkOutlined, DeleteOutlined, EllipsisOutlined } from "@ant-design/icons-vue";
import { SearchOutlined, ReloadOutlined, ApiOutlined, ArrowRightOutlined, DeleteOutlined } from "@ant-design/icons-vue";
import StatusBadge from "../components/StatusBadge.vue";
import SurfaceCard from "../components/SurfaceCard.vue";
@@ -10,22 +10,19 @@ import { formatDateTime, titleCaseToken } from "../lib/formatters";
import { desktopPublishMediaCatalog } from "../lib/media-catalog";
import type { RuntimeAccount } from "../types";
const { snapshot, refresh, loading } = useDesktopRuntime();
const { snapshot, refresh, refreshAccounts, loading } = useDesktopRuntime();
const selectedPlatform = ref("all");
const selectedStatus = ref("all");
const searchQuery = ref("");
const bindPendingPlatformId = ref<string | null>(null);
const consolePendingAccountId = ref<string | null>(null);
const unbindPendingAccountId = ref<string | null>(null);
const actionSuccess = ref<string | null>(null);
type AccountAuthState = "authorized" | "expired" | "attention" | "risk";
type AccountRow = Readonly<Omit<RuntimeAccount, "tags">> & { tags: readonly string[] };
const clientsById = computed(
() => new Map((snapshot.value?.clients ?? []).map((item) => [item.id, item])),
);
const accounts = computed(() => snapshot.value?.accounts ?? []);
const overview = computed(() => ({
@@ -112,6 +109,17 @@ function accountInitial(account: AccountRow): string {
return account.displayName.slice(0, 1).toUpperCase();
}
function sessionStateLabel(account: AccountRow): string {
switch (account.sessionState) {
case "hot":
return "活跃会话";
case "warm":
return "本地缓存";
default:
return "冷缓存";
}
}
const filteredAccounts = computed(() => {
const keyword = searchQuery.value.trim().toLowerCase();
@@ -128,12 +136,11 @@ const filteredAccounts = computed(() => {
return true;
}
const clientName = clientsById.value.get(account.clientId)?.deviceName ?? account.clientId;
const haystack = [
account.displayName,
account.platformUid,
platformMeta(account.platform).label,
clientName,
account.partition,
]
.join(" ")
.toLowerCase();
@@ -144,6 +151,7 @@ const filteredAccounts = computed(() => {
function selectPlatform(platform: string) {
selectedPlatform.value = platform;
void refreshAccounts();
}
function resetFilters() {
@@ -184,11 +192,26 @@ async function openConsole(account: AccountRow) {
}
}
async function unbindAccount(account: AccountRow) {
unbindPendingAccountId.value = account.id;
actionSuccess.value = null;
try {
await window.desktopBridge.app.unbindPublishAccount(account.id, account.syncVersion);
actionSuccess.value = `${account.displayName} 已解绑`;
await refresh();
} catch (error) {
showClientActionError("unbind-account", error);
} finally {
unbindPendingAccountId.value = null;
}
}
const tableColumns = [
{ title: "昵称", key: "name", dataIndex: "name" },
{ title: "平台", key: "platform", dataIndex: "platform" },
{ title: "授权状态", key: "status", dataIndex: "status" },
{ title: "归属客户端", key: "client", dataIndex: "client" },
{ title: "本地会话", key: "session", dataIndex: "session" },
{ title: "授权时间", key: "syncTime", dataIndex: "syncTime" },
{ title: "操作", key: "actions", align: "right" as const }
];
@@ -203,7 +226,7 @@ const tableColumns = [
<p>先把平台授权跑通上面直接发起真实登录绑定下面看已授权账号状态和进入创作台入口</p>
</div>
<div class="media-view__header-actions">
<a-button :loading="loading" @click="refresh">
<a-button :loading="loading" @click="refreshAccounts">
<template #icon><ReloadOutlined /></template>
刷新状态
</a-button>
@@ -292,7 +315,7 @@ const tableColumns = [
<div class="panel list-panel">
<div class="table-header">
<h3 class="panel-title">授权账号列表</h3>
<p class="panel-desc">当前 {{ filteredAccounts.length }} 个媒体账号支持重新授权和直接进入创作台</p>
<p class="panel-desc">当前仅展示本机存在缓存分区或会话的媒体账号并实时校验授权是否过期</p>
<div class="media-list-toolbar">
<div class="toolbar-filters">
@@ -317,7 +340,7 @@ const tableColumns = [
<a-input
v-model:value="searchQuery"
placeholder="搜索昵称、UID、平台或客户端"
placeholder="搜索昵称、UID、平台或本地分区"
style="width: 280px"
allow-clear
>
@@ -372,11 +395,11 @@ const tableColumns = [
</a-tag>
</template>
<template v-else-if="column.key === 'client'">
<template v-else-if="column.key === 'session'">
<div class="cell-primary-secondary">
<div class="info-stack">
<span class="title">{{ clientsById.get(record.clientId)?.deviceName ?? record.clientId }}</span>
<span class="subtitle">{{ record.online ? "在线" : "离线" }}</span>
<span class="title">{{ sessionStateLabel(record) }}</span>
<span class="subtitle">{{ record.online ? "本机在线" : "本机离线" }}</span>
</div>
</div>
</template>
@@ -407,6 +430,22 @@ const tableColumns = [
>
重新授权
</a-button>
<a-popconfirm
title="解绑这个账号?"
description="会删除当前账号绑定,并清理本机的本地 session / partition 缓存。"
ok-text="确认解绑"
cancel-text="取消"
@confirm="unbindAccount(record)"
>
<a-button
danger
size="small"
:loading="unbindPendingAccountId === record.id"
>
<template #icon><DeleteOutlined /></template>
解绑
</a-button>
</a-popconfirm>
</div>
</template>
</template>