feat(web): 迁移 creator 四页(竞品分析/采集设置/运营工作台),替换审计发现的空白 stub
- competitors:作品列表 + 七项筛选(补 api filterKeys 缺失的 published_at_status,归档版白名单漏项导致筛选被静默丢弃)+ 详情 + 素材三步处理与仿写流程
- settings:antd Tabs + Form 四类配置,数值字段清空提交 0 对齐归档 Number('') 语义
- workbench:评论/线索/规则/事件/私信/操作记录六页签,SSE 实时订阅 + 5s 重连(api 新增 creatorSubscribe),回复/私信草稿持久化、幂等 operation_key、逐次人工确认
- $id 路由与归档一致复用竞品分析页;删除遗留 settings.tsx/workbench.tsx 空白 stub
This commit is contained in:
+42
-2
@@ -1,6 +1,6 @@
|
||||
// 领域 API 层:资源路径与语义逐一对照 web.archived/src/shared/api/dataProvider.js。
|
||||
// 全部经由 requestErrorConfig.request(Basic Auth + 401 全局登出)。
|
||||
import { jsonOptions, request } from '@/requestErrorConfig';
|
||||
import { AUTH_STORAGE_KEY, RequestError, jsonOptions, request, unauthorized } from '@/requestErrorConfig';
|
||||
|
||||
export const resourcePaths = {
|
||||
browsers: '/browsers',
|
||||
@@ -33,7 +33,7 @@ const filterKeys: Partial<Record<Resource, string[]>> = {
|
||||
tasks: ['account_id', 'draft_id', 'state'],
|
||||
'creator-competitors': ['platform'],
|
||||
'creator-competitor-share-jobs': ['platform', 'status'],
|
||||
'creator-works': ['platform', 'source_id', 'source_type', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares'],
|
||||
'creator-works': ['platform', 'source_id', 'source_type', 'published_at_status', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares'],
|
||||
'creator-comments': ['platform', 'work_id'],
|
||||
'creator-leads': ['platform'],
|
||||
'creator-operations': ['account_id'],
|
||||
@@ -156,3 +156,43 @@ export function creatorUpdate(path: string, data: unknown) {
|
||||
export function creatorSyncConversation(id: string, limit = 200) {
|
||||
return request(`/creator/conversations/${encodeURIComponent(id)}/sync?limit=${encodeURIComponent(limit)}`, { method: 'POST' });
|
||||
}
|
||||
|
||||
// SSE 订阅:语义对齐归档版 dataProvider.creatorSubscribe(fetch 流式读取,按 data: 帧分发)。
|
||||
export async function creatorSubscribe(
|
||||
path: string,
|
||||
onMessage: (frame: string) => void,
|
||||
signal: AbortSignal,
|
||||
onStatus?: (status: 'connected' | 'disconnected') => void,
|
||||
): Promise<void> {
|
||||
const auth = localStorage.getItem(AUTH_STORAGE_KEY);
|
||||
const headers: Record<string, string> = {};
|
||||
if (auth) headers.Authorization = `Basic ${btoa(auth)}`;
|
||||
const response = await fetch(`/api${path}`, { headers, signal });
|
||||
if (response.status === 401) unauthorized();
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => ({}));
|
||||
throw new RequestError(body.error || `请求失败 (${response.status})`, response.status, body);
|
||||
}
|
||||
if (!response.body) throw new RequestError('业务更新流不可用', response.status);
|
||||
const reader = response.body.getReader();
|
||||
onStatus?.('connected');
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
const dispatch = (frame: string) => {
|
||||
if (frame.split(/\r\n|\n|\r/).some((line) => line.startsWith('data:'))) onMessage(frame);
|
||||
};
|
||||
for (;;) {
|
||||
const { value, done } = await reader.read();
|
||||
buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = /\r\n\r\n|\n\n|\r\r/.exec(buffer))) {
|
||||
dispatch(buffer.slice(0, match.index));
|
||||
buffer = buffer.slice(match.index + match[0].length);
|
||||
}
|
||||
if (done) {
|
||||
if (buffer.trim()) dispatch(buffer);
|
||||
onStatus?.('disconnected');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user