This commit is contained in:
949036910@qq.com
2026-05-28 15:24:21 +08:00
parent 1a32a59635
commit bf22177e80
6 changed files with 387 additions and 40 deletions

View File

@@ -18,6 +18,8 @@ const {
const feedback = ref({ severity: "", message: "" });
const scheduleDialogVisible = ref(false);
const scheduleDate = ref(null);
const accountDialogVisible = ref(false);
const activePlatformKey = ref("douyin");
const hasVideo = computed(
() => Boolean(processedVideoPath.value || generatedVideoPath.value),
@@ -40,6 +42,20 @@ const accountOptions = (platform) =>
value: a.id,
}));
const platformIcons = {
douyin: "📱",
kuaishou: "🎬",
shipin: "📺",
xiaohongshu: "📕",
};
const activePlatform = computed(
() =>
publishPlatforms.value.find((p) => p.key === activePlatformKey.value) ||
publishPlatforms.value[0] ||
null,
);
function showFeedback(result) {
if (!result?.message) return;
feedback.value = {
@@ -52,6 +68,17 @@ onMounted(async () => {
await workflow.refreshPublishAccounts();
});
function openAccountManager() {
if (publishPlatforms.value.length && !activePlatform.value) {
activePlatformKey.value = publishPlatforms.value[0].key;
}
accountDialogVisible.value = true;
}
function selectPlatform(platformKey) {
activePlatformKey.value = platformKey;
}
async function onPublish() {
feedback.value = { severity: "", message: "" };
showFeedback(await workflow.publishVideo({ autoPublish: true }));
@@ -85,22 +112,77 @@ function cancelSchedule() {
feedback.value = { severity: "success", message: "已取消定时发布" };
}
async function onLogin(platform) {
async function onLogin(platform, accountId = undefined) {
feedback.value = { severity: "", message: "" };
showFeedback(await workflow.loginPublishPlatform(platform.key));
showFeedback(await workflow.loginPublishPlatform(platform.key, accountId));
}
async function onLoginAccount(platform, account) {
await onLogin(platform, account.id);
}
async function onAddAccount(platform) {
await onLogin(platform, null);
}
function onDeleteAccount(platform, account) {
platform.accounts = (platform.accounts || []).filter((a) => a.id !== account.id);
if (platform.accountId === account.id) {
platform.accountId = null;
}
if (!platform.accounts.length) {
platform.loggedIn = false;
platform.nickname = "";
}
feedback.value = { severity: "success", message: "已从本地列表移除账号,刷新后可重新获取" };
}
function formatAccountStatus(account) {
if (account?.loginStatus === "active" && account?.hasCookies) {
return { dot: "🟢", text: "已登录", color: "#4ade80" };
}
if (account?.loginStatus === "expired") {
return { dot: "🟠", text: "登录过期", color: "#f59e0b" };
}
return { dot: "⚪", text: "未登录", color: "#999999" };
}
function formatUpdateTime(account) {
const raw = account?.updatedAt || account?.lastLoginAt || account?.createdAt;
if (!raw) return "";
const d = new Date(raw);
if (Number.isNaN(d.getTime())) return "";
return d.toLocaleString("zh-CN", {
month: "numeric",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
}
</script>
<template>
<DashboardCard title="视频发布" step="07" :grow="true">
<div class="mb-2 flex justify-end">
<Button
label="账号管理"
icon="pi pi-users"
size="small"
text
class="text-gray-400 hover:text-blue-400"
@click="openAccountManager"
/>
</div>
<div
v-for="platform in publishPlatforms"
:key="platform.key"
class="dashboard-platform-row mb-2"
class="publish-platform-row mb-2 flex items-center justify-between gap-2 rounded border border-white/5 bg-white/5 p-2 hover:bg-white/10"
>
<label class="flex shrink-0 items-center gap-2 text-sm" style="min-width: 86px">
<label class="flex shrink-0 items-center text-sm" style="min-width: 86px">
<Checkbox v-model="platform.checked" binary />
<span>{{ platform.label }}</span>
<span class="ml-1">{{ platform.label }}</span>
<span
v-if="platform.loggedIn"
class="text-xs text-emerald-400"
@@ -116,16 +198,9 @@ async function onLogin(platform) {
option-value="value"
placeholder="选择账号"
size="small"
class="min-w-0 flex-1"
class="min-w-0 max-w-[200px] flex-1"
:disabled="!accountOptions(platform).length"
/>
<Button
label="登录"
size="small"
text
:loading="publishLoggingIn === platform.key"
@click="onLogin(platform)"
/>
</div>
<p v-if="!hasVideo" class="mt-1 text-xs text-amber-400/90">
@@ -196,6 +271,94 @@ async function onLogin(platform) {
首次发布请手动关闭浏览器内出现的任何弹窗提示说明等防止干扰脚本下次即可流畅运行
</p>
<Dialog
v-model:visible="accountDialogVisible"
header="平台账号管理"
modal
:style="{ width: '640px' }"
>
<div class="account-manager">
<div class="platform-tabs">
<div
v-for="platform in publishPlatforms"
:key="platform.key"
class="platform-tab"
:class="{ active: activePlatform?.key === platform.key }"
@click="selectPlatform(platform.key)"
>
<span class="tab-icon">{{ platformIcons[platform.key] || "📌" }}</span>
<span class="tab-name">{{ platform.label }}</span>
<span v-if="platform.accounts?.length" class="tab-count">{{ platform.accounts.length }}</span>
</div>
</div>
<div class="account-list">
<div
v-for="account in activePlatform?.accounts || []"
:key="`${activePlatform.key}_${account.id}`"
class="account-card"
>
<div class="account-info">
<span class="status-dot">{{ formatAccountStatus(account).dot }}</span>
<div class="account-detail">
<div class="account-nickname">{{ account.nickname || "未登录" }}</div>
<div class="account-meta">
<span class="status-text" :style="{ color: formatAccountStatus(account).color }">
{{ formatAccountStatus(account).text }}
</span>
<span v-if="formatUpdateTime(account)" class="update-time">
· {{ formatUpdateTime(account) }}
</span>
</div>
</div>
</div>
<div class="account-actions">
<Button
label="扫码登录"
size="small"
severity="secondary"
:loading="publishLoggingIn === activePlatform.key"
@click="onLoginAccount(activePlatform, account)"
/>
<Button
label="删除"
size="small"
severity="danger"
outlined
@click="onDeleteAccount(activePlatform, account)"
/>
</div>
</div>
<div v-if="!(activePlatform?.accounts || []).length" class="account-card">
<div class="account-info">
<span class="status-dot"></span>
<div class="account-detail">
<div class="account-nickname">未登录</div>
<div class="account-meta">
<span class="status-text" style="color: #999999">未登录</span>
</div>
</div>
</div>
<div class="account-actions">
<Button
label="扫码登录"
size="small"
severity="secondary"
:loading="publishLoggingIn === activePlatform?.key"
@click="onAddAccount(activePlatform)"
/>
</div>
</div>
<div class="add-account-btn" @click="onAddAccount(activePlatform)">
<span class="add-icon">+</span>
<span>添加{{ activePlatform?.label || "" }}账号</span>
</div>
</div>
</div>
</Dialog>
<Dialog
v-model:visible="scheduleDialogVisible"
header="定时发布"
@@ -217,3 +380,118 @@ async function onLogin(platform) {
</Dialog>
</DashboardCard>
</template>
<style scoped>
.account-manager {
display: flex;
flex-direction: column;
gap: 12px;
}
.platform-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.platform-tab {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: 1px solid #333333;
border-radius: 10px;
background: #1c1c1c;
color: #d1d5db;
cursor: pointer;
transition: all 0.2s ease;
}
.platform-tab.active {
border-color: #2563eb;
background: rgba(37, 99, 235, 0.15);
color: #e2e8f0;
}
.tab-count {
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 999px;
background: rgba(37, 99, 235, 0.25);
color: #bfdbfe;
font-size: 11px;
line-height: 18px;
text-align: center;
}
.account-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.account-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px;
border: 1px solid #333333;
border-radius: 10px;
background: #1a1a1a;
}
.account-info {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.account-detail {
min-width: 0;
}
.account-nickname {
color: #e5e7eb;
font-size: 14px;
font-weight: 600;
}
.account-meta {
margin-top: 3px;
color: #9ca3af;
font-size: 12px;
}
.account-actions {
display: flex;
align-items: center;
gap: 8px;
}
.add-account-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px dashed #3f3f46;
border-radius: 10px;
padding: 10px 12px;
color: #cbd5e1;
cursor: pointer;
transition: all 0.2s ease;
}
.add-account-btn:hover {
border-color: #2563eb;
color: #dbeafe;
background: rgba(37, 99, 235, 0.08);
}
.add-icon {
font-size: 18px;
line-height: 1;
}
</style>

View File

@@ -54,15 +54,17 @@ export async function refreshPublishAccounts(store) {
* @param {ReturnType<typeof import('../stores/workflow.js').useWorkflowStore>} store
* @param {string} platformKey
*/
export async function loginPublishPlatform(store, platformKey) {
export async function loginPublishPlatform(store, platformKey, accountId = undefined) {
const row = store.publishPlatforms.find((p) => p.key === platformKey);
if (!row) return { ok: false, message: "未知平台" };
store.publishLoggingIn = platformKey;
try {
const result = await invoke("publish_login", {
platform: platformKey,
accountId: row.accountId || null,
params: {
platform: platformKey,
accountId: accountId === undefined ? row.accountId || null : accountId,
},
});
if (!result?.success) {
return {
@@ -116,9 +118,11 @@ export async function executePublishVideo(store, { autoPublish = true } = {}) {
for (const row of selected) {
const check = await invoke("publish_check_login", {
platform: row.key,
accountId: row.accountId || null,
checkBrowser: false,
params: {
platform: row.key,
accountId: row.accountId || null,
checkBrowser: false,
},
});
if (!check?.isLoggedIn) {
const label = row.label || row.key;
@@ -142,16 +146,18 @@ export async function executePublishVideo(store, { autoPublish = true } = {}) {
try {
const result = await invoke("publish_execute", {
platforms: selected.map((p) => ({
platform: p.key,
accountId: p.accountId || null,
})),
videoPath,
coverPath,
title: title.split(/\r?\n/)[0].slice(0, 30),
description,
tags,
autoPublish,
params: {
platforms: selected.map((p) => ({
platform: p.key,
accountId: p.accountId || null,
})),
videoPath,
coverPath,
title: title.split(/\r?\n/)[0].slice(0, 30),
description,
tags,
autoPublish,
},
});
store.publishResults = Array.isArray(result?.results) ? result.results : [];

View File

@@ -685,8 +685,8 @@ export const useWorkflowStore = defineStore("workflow", {
return refreshPublishAccounts(this);
},
async loginPublishPlatform(platformKey) {
return loginPublishPlatform(this, platformKey);
async loginPublishPlatform(platformKey, accountId = undefined) {
return loginPublishPlatform(this, platformKey, accountId);
},
async publishVideo(options) {