fix(handoff): P1 knowledge 落地闭环修复(ready 态 + returnUrl 契约)

KnowledgeHandoffLanding: token 用完即弃后(setPrecheck 清 handoffToken)以 precheckId
维持就绪态,兑现中不误退"会话失效"。
MarketAssetDetailPage: createHandoff 的 returnUrl 用绝对 URL(后端 validateReturnUrl
要求 scheme+host),前端 navigate 仍用相对 path。

均为 handoff-knowledge e2e 正路闭环验证依赖的修复(真后端 2 passed)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-22 10:35:38 -07:00
parent 610ba7ce6a
commit a09bf9cda3
2 changed files with 7 additions and 5 deletions

View File

@ -17,14 +17,15 @@ import { useKbBindPrecheck, useCreateKnowledgeBinding } from './useKnowledgeHand
*/
export default function KnowledgeHandoffLanding() {
const navigate = useNavigate();
const { handoffToken, assetId, targetWorkId, returnUrl, setPrecheck, clearSession } = useHandoffStore();
const { handoffToken, precheckId, assetId, targetWorkId, returnUrl, setPrecheck, clearSession } = useHandoffStore();
const bindPrecheck = useBindPrecheck(assetId ?? '');
const kbBindPrecheck = useKbBindPrecheck();
const createBinding = useCreateKnowledgeBinding();
const [error, setError] = useState<string | null>(null);
const [done, setDone] = useState(false);
const ready = Boolean(handoffToken && assetId && targetWorkId);
// 兑现中仍算就绪:setPrecheck 后 handoffToken 已清(用完即弃),但 precheckId 在 → 仍在兑现流程内,不应退回失效态
const ready = Boolean((handoffToken || precheckId) && assetId && targetWorkId);
const pending = bindPrecheck.isPending || kbBindPrecheck.isPending || createBinding.isPending;
async function confirmBinding() {

View File

@ -78,14 +78,15 @@ export default function MarketAssetDetailPage() {
// token 走 URL query 传到落地页,落地页随即 replaceState 清除(防刷新重复消费);authorizationSummaryId 取后端预检返回值。
async function startKnowledgeHandoff() {
if (!assetId || !targetWorkId || !precheck?.authorizationSummaryId) return;
const returnUrl = `/knowledge/${targetWorkId}`;
// 后端校验 returnUrl 需绝对 URL(scheme+host);前端落地/返回用相对 path(react-router navigate)。
const returnPath = `/knowledge/${targetWorkId}`;
const res = await createHandoff.mutateAsync({
assetId,
targetOwner: 'knowledge',
targetAction: 'bind',
targetWorkId: Number(targetWorkId),
authorizationSummaryId: precheck.authorizationSummaryId,
returnUrl,
returnUrl: `${window.location.origin}${returnPath}`,
});
if (!res.handoffToken) return;
const params = new URLSearchParams({
@ -93,7 +94,7 @@ export default function MarketAssetDetailPage() {
assetId,
targetWorkId,
action: 'bind',
returnUrl,
returnUrl: returnPath,
});
navigate(`/handoff/land/knowledge?${params.toString()}`);
}