feat: enhance platform management and synchronization in ArticleEditor and PublishArticle components
This commit is contained in:
@@ -8,10 +8,11 @@ import { useI18n } from "vue-i18n";
|
||||
|
||||
import PromptRuleModal from "@/components/PromptRuleModal.vue";
|
||||
import PublishPlatformSelector from "@/components/PublishPlatformSelector.vue";
|
||||
import { generateApi, promptRulesApi, schedulesApi } from "@/lib/api";
|
||||
import { generateApi, mediaApi, promptRulesApi, schedulesApi } from "@/lib/api";
|
||||
import { formatError } from "@/lib/errors";
|
||||
import {
|
||||
getAllPublishPlatforms,
|
||||
buildPublishPlatformOptions,
|
||||
normalizePublishPlatformIds,
|
||||
parseTargetPlatforms,
|
||||
serializeTargetPlatforms,
|
||||
} from "@/lib/publish-platforms";
|
||||
@@ -44,7 +45,26 @@ const form = reactive({
|
||||
});
|
||||
|
||||
const isSchedule = computed(() => props.mode === "schedule");
|
||||
const allPlatforms = computed(() => getAllPublishPlatforms());
|
||||
|
||||
const platformsQuery = useQuery({
|
||||
queryKey: ["media", "platforms", "task-drawer"],
|
||||
enabled: computed(() => props.open),
|
||||
queryFn: () => mediaApi.platforms(),
|
||||
});
|
||||
|
||||
const accountsQuery = useQuery({
|
||||
queryKey: ["media", "platform-accounts", "task-drawer"],
|
||||
enabled: computed(() => props.open),
|
||||
queryFn: () => mediaApi.accounts(),
|
||||
});
|
||||
|
||||
const allPlatforms = computed(() =>
|
||||
buildPublishPlatformOptions(platformsQuery.data.value ?? [], accountsQuery.data.value),
|
||||
);
|
||||
|
||||
const platformsLoading = computed(
|
||||
() => platformsQuery.isPending.value || accountsQuery.isPending.value,
|
||||
);
|
||||
|
||||
const rulesQuery = useQuery({
|
||||
queryKey: ["promptRules", "simple"],
|
||||
@@ -89,12 +109,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
const createScheduleMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
schedulesApi.create({
|
||||
name: form.name.trim(),
|
||||
prompt_rule_id: form.promptRuleId!,
|
||||
cron_expr: buildDailyCron(form.scheduleTime),
|
||||
target_platform: serializeTargetPlatforms(form.platformIds),
|
||||
}),
|
||||
schedulesApi.create(buildSchedulePayload()),
|
||||
onSuccess: async () => {
|
||||
message.success(t("custom.messages.scheduleCreated"));
|
||||
emit("update:open", false);
|
||||
@@ -105,12 +120,7 @@ const createScheduleMutation = useMutation({
|
||||
|
||||
const updateScheduleMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
schedulesApi.update(props.task!.id, {
|
||||
name: form.name.trim(),
|
||||
prompt_rule_id: form.promptRuleId!,
|
||||
cron_expr: buildDailyCron(form.scheduleTime),
|
||||
target_platform: serializeTargetPlatforms(form.platformIds),
|
||||
}),
|
||||
schedulesApi.update(props.task!.id, buildSchedulePayload()),
|
||||
onSuccess: async () => {
|
||||
message.success(t("custom.messages.scheduleUpdated"));
|
||||
emit("update:open", false);
|
||||
@@ -121,18 +131,7 @@ const updateScheduleMutation = useMutation({
|
||||
|
||||
const instantGenerateMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
generateApi.fromRule({
|
||||
prompt_rule_id: form.promptRuleId!,
|
||||
target_platform: serializeTargetPlatforms(form.platformIds),
|
||||
extra_params: {
|
||||
task_name: form.name.trim(),
|
||||
generation_mode: "instant",
|
||||
enable_web_search: form.enableWebSearch,
|
||||
generate_count: form.generateCount,
|
||||
target_platforms: form.platformIds,
|
||||
cover_file_name: coverFileName.value || null,
|
||||
},
|
||||
}),
|
||||
generateApi.fromRule(buildInstantPayload()),
|
||||
onSuccess: async () => {
|
||||
message.success(t("custom.messages.generateSubmitted"));
|
||||
emit("update:open", false);
|
||||
@@ -162,6 +161,36 @@ function hydrateForm(): void {
|
||||
resetCoverState();
|
||||
}
|
||||
|
||||
function getSelectedPlatformIds(): string[] {
|
||||
return normalizePublishPlatformIds(form.platformIds);
|
||||
}
|
||||
|
||||
function buildSchedulePayload() {
|
||||
return {
|
||||
name: form.name.trim(),
|
||||
prompt_rule_id: form.promptRuleId!,
|
||||
cron_expr: buildDailyCron(form.scheduleTime),
|
||||
target_platform: serializeTargetPlatforms(getSelectedPlatformIds()),
|
||||
};
|
||||
}
|
||||
|
||||
function buildInstantPayload() {
|
||||
const platformIds = getSelectedPlatformIds();
|
||||
|
||||
return {
|
||||
prompt_rule_id: form.promptRuleId!,
|
||||
target_platform: serializeTargetPlatforms(platformIds),
|
||||
extra_params: {
|
||||
task_name: form.name.trim(),
|
||||
generation_mode: "instant",
|
||||
enable_web_search: form.enableWebSearch,
|
||||
generate_count: form.generateCount,
|
||||
target_platforms: platformIds,
|
||||
cover_file_name: coverFileName.value || null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resetCoverState(): void {
|
||||
if (coverPreviewUrl.value) {
|
||||
URL.revokeObjectURL(coverPreviewUrl.value);
|
||||
@@ -323,7 +352,17 @@ function padTime(value: string): string {
|
||||
<div class="generate-task-drawer__field">
|
||||
<label class="generate-task-drawer__label">{{ t("custom.task.platforms") }}:</label>
|
||||
<p class="generate-task-drawer__hint">{{ t("custom.task.platformsHint") }}</p>
|
||||
<PublishPlatformSelector v-model="form.platformIds" :platforms="allPlatforms" />
|
||||
<a-skeleton
|
||||
v-if="platformsLoading"
|
||||
active
|
||||
:title="false"
|
||||
:paragraph="{ rows: 4 }"
|
||||
/>
|
||||
<PublishPlatformSelector
|
||||
v-else
|
||||
v-model="form.platformIds"
|
||||
:platforms="allPlatforms"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="generate-task-drawer__field">
|
||||
|
||||
@@ -9,11 +9,15 @@ import type {
|
||||
PublisherPublishResponse,
|
||||
PublisherPublishTaskResult,
|
||||
} from "@geo/shared-types";
|
||||
import { computed, h, ref, watch } from "vue";
|
||||
import { computed, h, ref, watch, watchEffect } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import { articlesApi, getApiBaseURL, mediaApi } from "@/lib/api";
|
||||
import { formatError } from "@/lib/errors";
|
||||
import {
|
||||
normalizePublishPlatformId,
|
||||
normalizePublishPlatformIds,
|
||||
} from "@/lib/publish-platforms";
|
||||
import { publishWithPublisherPlugin } from "@/lib/publisher-plugin";
|
||||
import { loadPublisherRuntimeState } from "@/lib/publisher-runtime";
|
||||
|
||||
@@ -39,6 +43,7 @@ const pluginVersion = ref<string | undefined>();
|
||||
const pluginInstallationId = ref<number | null>(null);
|
||||
const localPlatforms = ref<PublisherLocalPlatformState[]>([]);
|
||||
const fileList = ref<any[]>([]);
|
||||
const selectionHydrated = ref(false);
|
||||
|
||||
function handleBeforeUpload(file: File) {
|
||||
const url = URL.createObjectURL(file);
|
||||
@@ -84,27 +89,63 @@ watch(
|
||||
fileList.value = [];
|
||||
pluginInstallationId.value = null;
|
||||
localPlatforms.value = [];
|
||||
selectionHydrated.value = false;
|
||||
return;
|
||||
}
|
||||
selectionHydrated.value = false;
|
||||
await refreshRuntime();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.open || selectionHydrated.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
detailQuery.isPending.value ||
|
||||
accountsQuery.isPending.value ||
|
||||
platformsQuery.isPending.value ||
|
||||
runtimeLoading.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedPlatforms = new Set(normalizePublishPlatformIds(detailQuery.data.value?.platforms ?? []));
|
||||
selectedAccountIds.value = accountCards.value
|
||||
.filter((account) => account.accountId && account.selectable && selectedPlatforms.has(account.platformId))
|
||||
.map((account) => account.accountId as number);
|
||||
selectionHydrated.value = true;
|
||||
});
|
||||
|
||||
const localPlatformMap = computed(() => {
|
||||
return new Map(localPlatforms.value.map((item) => [item.platform_id, item]));
|
||||
return new Map(localPlatforms.value.map((item) => [normalizePublishPlatformId(item.platform_id), item]));
|
||||
});
|
||||
|
||||
const accountGroups = computed(() => {
|
||||
const groups = new Map<string, PlatformAccount[]>();
|
||||
for (const account of accountsQuery.data.value ?? []) {
|
||||
const platformId = normalizePublishPlatformId(account.platform_id);
|
||||
const list = groups.get(platformId) ?? [];
|
||||
list.push(account);
|
||||
groups.set(platformId, list);
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
|
||||
const platformNameMap = computed(() => {
|
||||
return new Map((platformsQuery.data.value ?? []).map((platform) => [platform.platform_id, platform.name]));
|
||||
return new Map(
|
||||
(platformsQuery.data.value ?? []).map((platform) => [normalizePublishPlatformId(platform.platform_id), platform.name]),
|
||||
);
|
||||
});
|
||||
|
||||
const accountCards = computed(() => {
|
||||
const accountMap = new Map((accountsQuery.data.value ?? []).map((account) => [account.platform_id, account]));
|
||||
|
||||
return (platformsQuery.data.value ?? []).map((platform) => {
|
||||
const account = accountMap.get(platform.platform_id) ?? null;
|
||||
const local = localPlatformMap.value.get(platform.platform_id);
|
||||
const platformId = normalizePublishPlatformId(platform.platform_id);
|
||||
const accounts = accountGroups.value.get(platformId) ?? [];
|
||||
const account = accounts[0] ?? null;
|
||||
const local = localPlatformMap.value.get(platformId);
|
||||
const localConnected = Boolean(local?.connected);
|
||||
const uidMatches = account
|
||||
? localConnected && (!local?.platform_uid || local.platform_uid === account.platform_uid)
|
||||
@@ -112,9 +153,9 @@ const accountCards = computed(() => {
|
||||
const selectable = Boolean(account && account.status === "active" && uidMatches && pluginInstalled.value);
|
||||
|
||||
return {
|
||||
key: account?.id ?? platform.platform_id,
|
||||
key: account?.id ?? platformId,
|
||||
accountId: account?.id ?? null,
|
||||
platformId: platform.platform_id,
|
||||
platformId,
|
||||
platformName: platform.name,
|
||||
platformShortName: platform.short_name,
|
||||
platformAccentColor: platform.accent_color,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import type { PublishPlatformOption } from "@/lib/publish-platforms";
|
||||
import { isPlatformBound } from "@/lib/publish-platforms";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: string[];
|
||||
@@ -75,9 +74,9 @@ function togglePlatform(platformId: string): void {
|
||||
|
||||
<span
|
||||
class="publish-platform-selector__status"
|
||||
:class="{ 'publish-platform-selector__status--bound': isPlatformBound(platform.id) }"
|
||||
:class="{ 'publish-platform-selector__status--bound': platform.bound }"
|
||||
>
|
||||
{{ isPlatformBound(platform.id) ? (platform.accountLabel ?? t("custom.task.platformConnected")) : t("custom.task.platformUnauthorized") }}
|
||||
{{ platform.bound ? (platform.accountLabel ?? t("custom.task.platformConnected")) : t("custom.task.platformUnauthorized") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user