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:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
import StatusBadge from "../components/StatusBadge.vue";
|
||||
import SurfaceCard from "../components/SurfaceCard.vue";
|
||||
@@ -7,61 +7,54 @@ import { useDesktopRuntime } from "../composables/useDesktopRuntime";
|
||||
import { formatRelativeTime, titleCaseToken } from "../lib/formatters";
|
||||
import { taskTone } from "../lib/runtime-ui";
|
||||
|
||||
const { snapshot, refresh } = useDesktopRuntime();
|
||||
const actionPendingTaskId = ref<string | null>(null);
|
||||
const actionError = ref<string | null>(null);
|
||||
|
||||
async function openReview(taskId: string) {
|
||||
actionPendingTaskId.value = taskId;
|
||||
actionError.value = null;
|
||||
try {
|
||||
await window.desktopBridge.app.openTaskReview(taskId);
|
||||
} catch (error) {
|
||||
actionError.value = error instanceof Error ? error.message : "打开审核页失败";
|
||||
} finally {
|
||||
actionPendingTaskId.value = null;
|
||||
}
|
||||
function translatePlatform(platform: string) {
|
||||
const map: Record<string, string> = {
|
||||
toutiaohao: "头条号",
|
||||
bilibili: "哔哩哔哩",
|
||||
xiaohongshu: "小红书",
|
||||
douyin: "抖音",
|
||||
kuaishou: "快手",
|
||||
wechat_oa: "微信公众号",
|
||||
baijiahao: "百家号",
|
||||
zhihu: "知乎",
|
||||
};
|
||||
return map[platform?.toLowerCase()] || titleCaseToken(platform);
|
||||
}
|
||||
|
||||
async function resolveParkedTask(taskId: string, status: "succeeded" | "failed") {
|
||||
actionPendingTaskId.value = taskId;
|
||||
actionError.value = null;
|
||||
try {
|
||||
await window.desktopBridge.app.resolveParkedTask(taskId, status);
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
actionError.value = error instanceof Error ? error.message : "回写审核结果失败";
|
||||
} finally {
|
||||
actionPendingTaskId.value = null;
|
||||
}
|
||||
function translateTaskStatus(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
queued: "等待排队",
|
||||
in_progress: "执行中",
|
||||
succeeded: "成功",
|
||||
failed: "失败",
|
||||
unknown: "未知异常",
|
||||
aborted: "已取消",
|
||||
};
|
||||
return map[status?.toLowerCase()] || status;
|
||||
}
|
||||
|
||||
const { snapshot } = useDesktopRuntime();
|
||||
|
||||
const columns = computed(() => {
|
||||
const tasks = snapshot.value?.tasks ?? [];
|
||||
return [
|
||||
{
|
||||
key: "queued",
|
||||
title: "Queued",
|
||||
title: "等待派发 (Queued)",
|
||||
description: "已经落库,等待 RabbitMQ 唤醒或客户端主动 lease。",
|
||||
items: tasks.filter((item) => item.status === "queued"),
|
||||
},
|
||||
{
|
||||
key: "in_progress",
|
||||
title: "In Progress",
|
||||
title: "正在执行 (In Progress)",
|
||||
description: "当前被 client 持有活动租约,结果回写必须带 lease_token。",
|
||||
items: tasks.filter((item) => item.status === "in_progress"),
|
||||
},
|
||||
{
|
||||
key: "waiting_user",
|
||||
title: "Waiting User",
|
||||
description: "已释放 lease,不自动迁移,只允许原 target_client 恢复。",
|
||||
items: tasks.filter((item) => item.status === "waiting_user"),
|
||||
},
|
||||
{
|
||||
key: "exception",
|
||||
title: "Fallback / Terminal",
|
||||
title: "异常与终态 (Terminal)",
|
||||
description: "unknown、failed、succeeded 等终态或补偿态。",
|
||||
items: tasks.filter((item) => !["queued", "in_progress", "waiting_user"].includes(item.status)),
|
||||
items: tasks.filter((item) => !["queued", "in_progress"].includes(item.status)),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -74,7 +67,7 @@ const columns = computed(() => {
|
||||
<p class="eyebrow">WORKFLOW CENTER</p>
|
||||
<h2>任务调度中心</h2>
|
||||
<p class="hero-summary">
|
||||
基于 Lease-based 的可靠端云协议泳道。全局监控本地客户端在 MQ 下发、本地锁抢占、及人工介入补偿各个流转阶段的阻塞情况。
|
||||
基于 lease 的可靠端云协议泳道。这里更偏底层调度视角,用来观察 MQ 下发、租约占用与 fallback 消费的运行阶段。
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
@@ -87,61 +80,29 @@ const columns = computed(() => {
|
||||
:title="column.title"
|
||||
:description="column.description"
|
||||
>
|
||||
<div class="lane-stack">
|
||||
<div class="lane-stack" :class="{ 'scrollable-lane': column.items.length > 0 }">
|
||||
<article v-for="task in column.items" :key="task.id" class="task-card">
|
||||
<div class="task-head">
|
||||
<div>
|
||||
<div class="task-identity-copy">
|
||||
<strong>{{ task.title }}</strong>
|
||||
<p>{{ task.accountName }} · {{ titleCaseToken(task.platform) }}</p>
|
||||
<p>{{ task.accountName }} · {{ translatePlatform(task.platform) }}</p>
|
||||
</div>
|
||||
<StatusBadge :tone="taskTone(task.status)" :label="task.status" />
|
||||
<StatusBadge :tone="taskTone(task.status)" :label="translateTaskStatus(task.status)" class="shrink-badge" />
|
||||
</div>
|
||||
|
||||
<div class="task-flags">
|
||||
<StatusBadge tone="brand" :label="task.routing" subtle />
|
||||
<StatusBadge tone="info" :label="task.kind" subtle />
|
||||
<StatusBadge
|
||||
:tone="task.mode === 'manual' ? 'warn' : 'success'"
|
||||
:label="task.mode"
|
||||
subtle
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="task-summary">{{ task.summary }}</p>
|
||||
|
||||
<div v-if="task.status === 'waiting_user'" class="task-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="action-button secondary"
|
||||
:disabled="actionPendingTaskId === task.id"
|
||||
@click="openReview(task.id)"
|
||||
>
|
||||
打开审核页
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="action-button success"
|
||||
:disabled="actionPendingTaskId === task.id"
|
||||
@click="resolveParkedTask(task.id, 'succeeded')"
|
||||
>
|
||||
标记已发布
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="action-button danger"
|
||||
:disabled="actionPendingTaskId === task.id"
|
||||
@click="resolveParkedTask(task.id, 'failed')"
|
||||
>
|
||||
标记失败
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<footer class="task-footer">
|
||||
<span>
|
||||
{{
|
||||
task.leaseExpiresAt
|
||||
? `lease ${formatRelativeTime(task.leaseExpiresAt)}`
|
||||
: "lease released"
|
||||
? `${formatRelativeTime(task.leaseExpiresAt)} 过期`
|
||||
: "已释放"
|
||||
}}
|
||||
</span>
|
||||
<small>{{ formatRelativeTime(task.updatedAt) }}</small>
|
||||
@@ -149,7 +110,6 @@ const columns = computed(() => {
|
||||
</article>
|
||||
<p v-if="column.items.length === 0" class="empty-text">当前泳道为空。</p>
|
||||
</div>
|
||||
<p v-if="actionError" class="error-text">{{ actionError }}</p>
|
||||
</SurfaceCard>
|
||||
</div>
|
||||
</section>
|
||||
@@ -157,12 +117,31 @@ const columns = computed(() => {
|
||||
|
||||
<style scoped>
|
||||
.page,
|
||||
.lane-grid,
|
||||
.lane-stack {
|
||||
.lane-grid {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.lane-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.scrollable-lane {
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.scrollable-lane::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.scrollable-lane::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: block;
|
||||
}
|
||||
@@ -228,11 +207,22 @@ h2 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.task-identity-copy {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.task-head strong {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.shrink-badge {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-head p,
|
||||
|
||||
Reference in New Issue
Block a user