feat: finish Douyin production readiness flow

This commit is contained in:
2026-09-15 09:20:51 +08:00
parent 5867aac20f
commit 977e541fef
28 changed files with 1257 additions and 118 deletions
+11
View File
@@ -317,6 +317,17 @@ describe("creator pages", () => {
);
});
it("subscribes to realtime updates on non-DM operational tabs", async () => {
const creatorSubscribe = vi.fn(() => new Promise(() => {}));
const dataProvider = provider({ creatorSubscribe });
renderPage(<CreatorWorkbenchPage />, dataProvider);
await waitFor(() => expect(creatorSubscribe).toHaveBeenCalledTimes(1));
fireEvent.click(await screen.findByRole("tab", { name: "事件监听" }));
await waitFor(() => expect(creatorSubscribe).toHaveBeenCalledTimes(2));
fireEvent.click(screen.getByRole("tab", { name: "操作记录" }));
await waitFor(() => expect(creatorSubscribe).toHaveBeenCalledTimes(3));
});
it("isolates private messages by account and confirms one durable send", async () => {
const conversation = {
id: "conversation-a",
+54 -11
View File
@@ -123,6 +123,7 @@ export function CreatorWorkbenchPage() {
const [messagePending, setMessagePending] = useState(false);
const [messageError, setMessageError] = useState(null);
const [messageRetry, setMessageRetry] = useState(0);
const [messageSyncing, setMessageSyncing] = useState(false);
const [updateStatus, setUpdateStatus] = useState("disconnected");
const [conversationID, setConversationID] = useState("");
const [dmAccountID, setDmAccountID] = useState("");
@@ -300,13 +301,42 @@ export function CreatorWorkbenchPage() {
if (sequence === messageSequence.current) setMessagePending(false);
};
}, [conversationID, tab, dataProvider, messageRetry, messagePage]);
const syncMessages = async () => {
if (!conversationID || messageSyncing) return;
setMessageSyncing(true);
setMessageError(null);
try {
const result = await dataProvider.creatorSyncConversation(conversationID);
setNotice({
variant: result?.history_has_more ? "warning" : "success",
text: result?.history_has_more
? `已同步 ${result?.messages ?? 0} 条平台私信历史,仍有更多内容待同步。`
: `已同步 ${result?.messages ?? 0} 条平台私信历史。`,
});
setMessageRetry((value) => value + 1);
} catch (syncError) {
setMessageError(syncError);
} finally {
setMessageSyncing(false);
}
};
useEffect(() => {
if (reply.target_comment_id) {
writeDraft(replyDraftKey(reply.target_comment_id), reply);
}
}, [reply]);
useEffect(() => {
if (tab !== "dms" || typeof dataProvider.creatorSubscribe !== "function") {
const realtimeTabs = new Set([
"comments",
"leads",
"events",
"dms",
"operations",
]);
if (
!realtimeTabs.has(tab) ||
typeof dataProvider.creatorSubscribe !== "function"
) {
setUpdateStatus("disconnected");
return undefined;
}
@@ -321,7 +351,9 @@ export function CreatorWorkbenchPage() {
"/creator/updates",
() => {
load();
setMessageRetry((value) => value + 1);
if (tab === "dms") {
setMessageRetry((value) => value + 1);
}
},
controller.signal,
(status) => setUpdateStatus(status),
@@ -343,7 +375,7 @@ export function CreatorWorkbenchPage() {
window.clearTimeout(retryTimer);
setUpdateStatus("disconnected");
};
}, [tab, dmAccountID, dataProvider]);
}, [tab, dataProvider]);
useEffect(() => {
if (!dmAccountID || !conversationID) return;
const draft = readDraft(dmDraftKey(dmAccountID, conversationID));
@@ -1135,14 +1167,25 @@ export function CreatorWorkbenchPage() {
}
/>
</div>
<Button
size="sm"
variant="outline"
onClick={() => setMessageRetry((value) => value + 1)}
busy={messagePending}
>
刷新
</Button>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => setMessageRetry((value) => value + 1)}
busy={messagePending}
>
刷新
</Button>
<Button
size="sm"
variant="outline"
onClick={syncMessages}
busy={messageSyncing}
disabled={!conversationID}
>
同步平台历史
</Button>
</div>
</div>
<PageState
pending={messagePending}
+6
View File
@@ -251,6 +251,12 @@ export const dataProvider = {
creatorGet(path) {
return request(path);
},
creatorSyncConversation(id, limit = 200) {
return request(
`/creator/conversations/${encodeURIComponent(id)}/sync?limit=${encodeURIComponent(limit)}`,
{ method: "POST" },
);
},
async creatorSubscribe(path, onMessage, signal, onStatus) {
const auth = localStorage.getItem("creatorhub.auth");
const headers = {};