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:
@@ -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 });
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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 || []);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user