feat: EveryPublish Web 管理端重构与全平台发布接入

- 新增 React + TDesign 管理端(sau_web_react),替代历史 Vue 前端
- 后端接入 11 平台登录/发布(视频+图文),check_cookie 扩展全平台
- 异步发布任务 + 实时进度监控 + 发布日志
- Open API 开放体系(API Key 鉴权 + 7 个开放接口 + API 文档页)
- 账号单/批量校验与批量删除
- 全局配置(登录方式 + 发布静默模式)
- 统一浏览器为系统 Chrome,品牌文案统一为 EveryPublish
This commit is contained in:
Qiufeng
2026-08-21 14:41:47 +08:00
parent 1c66b7db4b
commit fd944b08bd
120 changed files with 6684 additions and 210 deletions
+77
View File
@@ -0,0 +1,77 @@
import request from 'utils/request';
import { getPlatformByType } from 'configs/platforms';
export interface IAccount {
id: number;
type: number;
filePath: string;
name: string;
/** -1 验证中 1 正常 0 异常 */
status: number;
platform: string;
}
/** 登录 SSE 直连地址(EventSource 不走 axios,需完整 URL,后端 CORS 已开) */
export const LOGIN_SSE_BASE = 'http://127.0.0.1:5409';
const normalize = (row: any[]): IAccount => ({
id: row[0],
type: row[1],
filePath: row[2],
name: row[3],
status: row[4],
platform: getPlatformByType(row[1])?.name || '未知',
});
/** 快速获取账号列表(不校验 cookie) */
export const getAccounts = async (): Promise<IAccount[]> => {
const result: any = await request.get('/api/getAccounts');
const rows = result?.data || [];
return rows.map(normalize);
};
/** 获取账号列表并逐个校验 cookie 有效性(较慢) */
export const getValidAccounts = async (): Promise<IAccount[]> => {
const result: any = await request.get('/api/getValidAccounts');
const rows = result?.data || [];
return rows.map(normalize);
};
export const deleteAccount = (id: number) => request.get('/api/deleteAccount?id=' + id);
export const updateAccount = (data: { id: number; type: number; userName: string }) =>
request.post('/api/updateUserinfo', data);
export const downloadCookie = (filePath: string) => {
window.open('/api/downloadCookie?filePath=' + encodeURIComponent(filePath), '_blank');
};
export const uploadCookie = (formData: FormData) =>
request.post('/api/uploadCookie', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
export const getLoginSSEUrl = (type: number, id: string) =>
LOGIN_SSE_BASE + '/login?type=' + type + '&id=' + encodeURIComponent(id);
export interface ICheckTask {
id: string;
ids: number[];
total: number;
done: number;
status: 'running' | 'done' | 'error';
results: Record<string, { valid: boolean | null; name: string }>;
logs: string[];
error: string | null;
}
/** 校验指定账号(单个或多个),返回校验任务 ID */
export const checkAccounts = (ids: number[]): Promise<{ taskId: string }> =>
request.post('/api/checkAccounts', { ids }).then((r: any) => r?.data || {});
/** 查询校验任务进度 */
export const getCheckStatus = (taskId: string): Promise<ICheckTask | null> =>
request.get('/api/checkAccounts/status?taskId=' + taskId).then((r: any) => r?.data || null);
/** 批量删除账号 */
export const deleteAccounts = (ids: number[]) => request.post('/api/deleteAccounts', { ids });
+25
View File
@@ -0,0 +1,25 @@
import request from 'utils/request';
export interface IMaterial {
id: number;
filename: string;
filesize: number;
upload_time: string;
file_path: string;
uuid: string;
}
export const getMaterials = async (): Promise<IMaterial[]> => {
const result: any = await request.get('/api/getFiles');
return result?.data || [];
};
export const uploadMaterial = (formData: FormData) =>
request.post('/api/uploadSave', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 0,
});
export const deleteMaterial = (id: number) => request.get('/api/deleteFile?id=' + id);
export const getFileUrl = (filename: string) => '/api/getFile?filename=' + encodeURIComponent(filename);
+17
View File
@@ -0,0 +1,17 @@
import request from 'utils/request';
export interface IApiKey {
id: number;
key: string;
name: string;
enabled: number;
created_at: string;
}
export const getApiKeys = (): Promise<IApiKey[]> =>
request.get('/api/open/keys').then((r: any) => r?.data || []);
export const createApiKey = (name: string): Promise<{ key: string }> =>
request.post('/api/open/keys', { name }).then((r: any) => r?.data || {});
export const deleteApiKey = (id: number) => request.delete('/api/open/keys/' + id);
+18
View File
@@ -0,0 +1,18 @@
import request from 'utils/request';
import { PLATFORMS, IPlatform } from 'configs/platforms';
/**
* 平台能力清单。优先返回后端 /platforms 元数据(体现 Web 与 CLI 主线同步),
* 后端不可用/未就绪时回退到本地静态配置。
*/
export const getPlatforms = async (): Promise<IPlatform[]> => {
try {
const result: any = await request.get('/api/platforms');
if (result?.data && Array.isArray(result.data) && result.data.length > 0) {
return result.data as IPlatform[];
}
return PLATFORMS;
} catch {
return PLATFORMS;
}
};
+49
View File
@@ -0,0 +1,49 @@
import request from 'utils/request';
export interface IPublishPayload {
type: number;
title: string;
/** 素材文件名列表 */
fileList: string[];
/** 账号 cookie 文件名列表 */
accountList: string[];
tags?: string[];
category?: number | null;
enableTimer?: boolean;
videosPerDay?: number;
dailyTimes?: number[];
startDays?: number;
productLink?: string;
productTitle?: string;
thumbnail?: string;
isDraft?: boolean;
desc?: string;
}
export interface IPublishTask {
id: string;
status: 'pending' | 'running' | 'success' | 'failed';
step: string;
progress: number;
logs: string[];
error: string | null;
type: number;
title: string;
created_at: string;
}
/** 提交单渠道发布,返回任务 ID */
export const postVideo = (data: IPublishPayload): Promise<{ taskId: string }> =>
request.post('/api/postVideo', data).then((r: any) => r?.data || {});
/** 批量提交发布,返回任务 ID 列表 */
export const postVideoBatch = (data: IPublishPayload[]): Promise<{ taskIds: string[] }> =>
request.post('/api/postVideoBatch', data).then((r: any) => r?.data || {});
/** 查询单个任务进度 */
export const getPublishStatus = (taskId: string): Promise<IPublishTask | null> =>
request.get('/api/publish/status?taskId=' + taskId).then((r: any) => r?.data || null);
/** 查询历史发布任务列表 */
export const getPublishTasks = (): Promise<IPublishTask[]> =>
request.get('/api/publish/tasks').then((r: any) => r?.data || []);
+11
View File
@@ -0,0 +1,11 @@
import request from 'utils/request';
export interface ISettings {
login_mode: string; // 'qr' 扫码登录 | 'browser' 浏览器登录
publish_headless: boolean;
}
export const getSettings = (): Promise<ISettings> =>
request.get('/settings').then((r: any) => r?.data || {});
export const updateSettings = (data: Partial<ISettings>) => request.post('/settings', data);