合并最新 master 并解决画布共享冲突
同步 origin/master 的场景、音效、运行时与后端原子提交改动 保留共享画布包导出并补齐场景、生成配方和历史动作类型 融合清单刷新测试、幂等请求、生成合同校验与项目记忆文档
This commit is contained in:
@@ -11,7 +11,10 @@ export function AccountWalletBar({
|
||||
return (
|
||||
<div className="launcher-account-bar" aria-label="账户资产">
|
||||
<PlatformMudPointWalletEntry
|
||||
balance={controller.mudPointBalance?.totalPoints ?? null}
|
||||
balance={
|
||||
controller.mudPointBalance?.totalPoints ??
|
||||
controller.legacyWalletBalance
|
||||
}
|
||||
breakdown={controller.mudPointBalance}
|
||||
isLoading={controller.mudPointBalanceStatus === 'loading'}
|
||||
error={controller.mudPointBalanceError || null}
|
||||
@@ -49,7 +52,11 @@ export function AccountWalletDialogs({
|
||||
{controller.walletLedgerOpen ? (
|
||||
<PlatformProfileWalletLedgerModal
|
||||
ledger={controller.walletLedger}
|
||||
fallbackBalance={controller.mudPointBalance?.totalPoints ?? 0}
|
||||
fallbackBalance={
|
||||
controller.mudPointBalance?.totalPoints ??
|
||||
controller.legacyWalletBalance ??
|
||||
0
|
||||
}
|
||||
isLoading={controller.walletLedgerLoading}
|
||||
error={controller.walletLedgerError}
|
||||
onClose={() => controller.setWalletLedgerOpen(false)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { PlatformProfileRechargeNativePaymentState } from '../../../../../packages/shared/src/components/PlatformProfileRechargeModal';
|
||||
import type {
|
||||
@@ -17,10 +17,15 @@ import { useWalletStore } from '../../stores/useWalletStore';
|
||||
|
||||
export function useAccountWallet(currentUserId: string) {
|
||||
const {
|
||||
ownerUserId,
|
||||
mudPointBalance,
|
||||
legacyWalletBalance,
|
||||
mudPointBalanceStatus,
|
||||
mudPointBalanceError,
|
||||
setWalletOwner,
|
||||
captureWalletBalanceSnapshot,
|
||||
applyWalletBalanceSnapshot,
|
||||
applyLegacyWalletBalanceSnapshot,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
resetWalletBalance,
|
||||
} = useWalletStore();
|
||||
@@ -41,32 +46,98 @@ export function useAccountWallet(currentUserId: string) {
|
||||
const [nativeRechargePayment, setNativeRechargePayment] =
|
||||
useState<PlatformProfileRechargeNativePaymentState | null>(null);
|
||||
const rechargeLifecycleRef = useRef(0);
|
||||
const walletLedgerLifecycleRef = useRef(0);
|
||||
const [walletUiOwnerUserId, setWalletUiOwnerUserId] = useState(currentUserId);
|
||||
const currentUserIdRef = useRef(currentUserId);
|
||||
const walletOwnerMatchesCurrentUser =
|
||||
Boolean(currentUserId) && ownerUserId === currentUserId;
|
||||
const walletUiOwnerMatchesCurrentUser =
|
||||
Boolean(currentUserId) && walletUiOwnerUserId === currentUserId;
|
||||
const walletUiIsVisible =
|
||||
walletOwnerMatchesCurrentUser && walletUiOwnerMatchesCurrentUser;
|
||||
const visibleMudPointBalance = walletOwnerMatchesCurrentUser
|
||||
? mudPointBalance
|
||||
: null;
|
||||
const visibleLegacyWalletBalance = walletOwnerMatchesCurrentUser
|
||||
? legacyWalletBalance
|
||||
: null;
|
||||
const visibleMudPointBalanceStatus = walletOwnerMatchesCurrentUser
|
||||
? mudPointBalanceStatus
|
||||
: 'idle';
|
||||
const visibleMudPointBalanceError = walletOwnerMatchesCurrentUser
|
||||
? mudPointBalanceError
|
||||
: '';
|
||||
const visibleWalletBalance =
|
||||
visibleMudPointBalance?.totalPoints ?? visibleLegacyWalletBalance;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
currentUserIdRef.current = currentUserId;
|
||||
rechargeLifecycleRef.current += 1;
|
||||
walletLedgerLifecycleRef.current += 1;
|
||||
}, [currentUserId]);
|
||||
|
||||
useEffect(() => {
|
||||
resetWalletBalance();
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
setWalletOwner(currentUserId || null);
|
||||
if (currentUserId) {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
}
|
||||
|
||||
const refreshWalletBalance = () => {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
if (currentUserId) {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
}
|
||||
};
|
||||
window.addEventListener('focus', refreshWalletBalance);
|
||||
return () => {
|
||||
window.removeEventListener('focus', refreshWalletBalance);
|
||||
};
|
||||
}, [currentUserId, onWalletBalanceMayHaveChanged, resetWalletBalance]);
|
||||
}, [currentUserId, onWalletBalanceMayHaveChanged, setWalletOwner]);
|
||||
|
||||
useEffect(() => {
|
||||
setWalletUiOwnerUserId(currentUserId);
|
||||
setWalletLedgerOpen(false);
|
||||
setWalletLedger(null);
|
||||
setWalletLedgerLoading(false);
|
||||
setWalletLedgerError(null);
|
||||
setRechargeOpen(false);
|
||||
setRechargeContent(null);
|
||||
setRechargeLoading(false);
|
||||
setRechargeError(null);
|
||||
setSubmittingRechargeProductId(null);
|
||||
setNativeRechargePayment(null);
|
||||
}, [currentUserId]);
|
||||
|
||||
async function loadWalletLedger() {
|
||||
const walletLedgerLifecycle = ++walletLedgerLifecycleRef.current;
|
||||
const snapshotOwnerUserId = currentUserId;
|
||||
setWalletLedgerLoading(true);
|
||||
setWalletLedgerError(null);
|
||||
try {
|
||||
setWalletLedger(await getClientProfileWalletLedger());
|
||||
const ledger = await getClientProfileWalletLedger();
|
||||
if (
|
||||
walletLedgerLifecycleRef.current === walletLedgerLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setWalletLedger(ledger);
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
walletLedgerLifecycleRef.current !== walletLedgerLifecycle ||
|
||||
currentUserIdRef.current !== snapshotOwnerUserId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setWalletLedger(null);
|
||||
setWalletLedgerError(
|
||||
error instanceof Error ? error.message : '读取泥点账单失败',
|
||||
);
|
||||
} finally {
|
||||
setWalletLedgerLoading(false);
|
||||
if (
|
||||
walletLedgerLifecycleRef.current === walletLedgerLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setWalletLedgerLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,37 +146,55 @@ export function useAccountWallet(currentUserId: string) {
|
||||
void loadWalletLedger();
|
||||
}
|
||||
|
||||
function applyRechargeContent(center: ProfileRechargeCenterResponse) {
|
||||
function applyRechargeContent(
|
||||
walletSnapshot: ReturnType<typeof captureWalletBalanceSnapshot>,
|
||||
center: ProfileRechargeCenterResponse,
|
||||
) {
|
||||
const {
|
||||
walletBalance,
|
||||
mudPointBalance: nextMudPointBalance,
|
||||
...content
|
||||
} = center;
|
||||
void walletBalance;
|
||||
if (nextMudPointBalance) {
|
||||
applyWalletBalanceSnapshot(nextMudPointBalance);
|
||||
if (nextMudPointBalance && walletSnapshot) {
|
||||
applyWalletBalanceSnapshot(walletSnapshot, nextMudPointBalance);
|
||||
} else if (walletSnapshot && Number.isFinite(walletBalance)) {
|
||||
applyLegacyWalletBalanceSnapshot(walletSnapshot, walletBalance);
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
} else {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
}
|
||||
setRechargeContent(content);
|
||||
}
|
||||
|
||||
async function loadRechargeCenter() {
|
||||
const snapshotOwnerUserId = currentUserId;
|
||||
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
|
||||
const rechargeLifecycle = rechargeLifecycleRef.current;
|
||||
setRechargeLoading(true);
|
||||
setRechargeError(null);
|
||||
try {
|
||||
const center = await getClientProfileRechargeCenter();
|
||||
if (rechargeLifecycleRef.current !== rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current !== rechargeLifecycle ||
|
||||
currentUserIdRef.current !== snapshotOwnerUserId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
applyRechargeContent(center);
|
||||
applyRechargeContent(walletSnapshot, center);
|
||||
} catch (error) {
|
||||
if (rechargeLifecycleRef.current === rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current === rechargeLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setRechargeError(
|
||||
error instanceof Error ? error.message : '读取泥点购买信息失败',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (rechargeLifecycleRef.current === rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current === rechargeLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setRechargeLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -130,16 +219,21 @@ export function useAccountWallet(currentUserId: string) {
|
||||
return;
|
||||
}
|
||||
const rechargeLifecycle = rechargeLifecycleRef.current;
|
||||
const snapshotOwnerUserId = currentUserId;
|
||||
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
|
||||
setSubmittingRechargeProductId(product.productId);
|
||||
setRechargeError(null);
|
||||
try {
|
||||
const response = await createClientProfileRechargeOrder(
|
||||
product.productId,
|
||||
);
|
||||
if (rechargeLifecycleRef.current !== rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current !== rechargeLifecycle ||
|
||||
currentUserIdRef.current !== snapshotOwnerUserId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
applyRechargeContent(response.center);
|
||||
applyRechargeContent(walletSnapshot, response.center);
|
||||
const nativePayment = response.wechatNativePayment;
|
||||
const codeUrl = nativePayment?.codeUrl?.trim();
|
||||
const expiresAt = nativePayment?.expiresAt?.trim();
|
||||
@@ -155,11 +249,17 @@ export function useAccountWallet(currentUserId: string) {
|
||||
isConfirming: false,
|
||||
});
|
||||
} catch (error) {
|
||||
if (rechargeLifecycleRef.current === rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current === rechargeLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setRechargeError(error instanceof Error ? error.message : '充值失败');
|
||||
}
|
||||
} finally {
|
||||
if (rechargeLifecycleRef.current === rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current === rechargeLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setSubmittingRechargeProductId(null);
|
||||
}
|
||||
}
|
||||
@@ -170,6 +270,8 @@ export function useAccountWallet(currentUserId: string) {
|
||||
return;
|
||||
}
|
||||
const rechargeLifecycle = rechargeLifecycleRef.current;
|
||||
const snapshotOwnerUserId = currentUserId;
|
||||
const walletSnapshot = captureWalletBalanceSnapshot(snapshotOwnerUserId);
|
||||
const orderId = nativeRechargePayment.orderId;
|
||||
setNativeRechargePayment((current) =>
|
||||
current?.orderId === orderId
|
||||
@@ -178,10 +280,13 @@ export function useAccountWallet(currentUserId: string) {
|
||||
);
|
||||
try {
|
||||
const response = await confirmClientWechatProfileRechargeOrder(orderId);
|
||||
if (rechargeLifecycleRef.current !== rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current !== rechargeLifecycle ||
|
||||
currentUserIdRef.current !== snapshotOwnerUserId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
applyRechargeContent(response.center);
|
||||
applyRechargeContent(walletSnapshot, response.center);
|
||||
if (response.order.status === 'paid') {
|
||||
setNativeRechargePayment(null);
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
@@ -199,7 +304,10 @@ export function useAccountWallet(currentUserId: string) {
|
||||
: current,
|
||||
);
|
||||
} catch {
|
||||
if (rechargeLifecycleRef.current === rechargeLifecycle) {
|
||||
if (
|
||||
rechargeLifecycleRef.current === rechargeLifecycle &&
|
||||
currentUserIdRef.current === snapshotOwnerUserId
|
||||
) {
|
||||
setNativeRechargePayment((current) =>
|
||||
current?.orderId === orderId
|
||||
? {
|
||||
@@ -214,29 +322,35 @@ export function useAccountWallet(currentUserId: string) {
|
||||
}
|
||||
|
||||
return {
|
||||
mudPointBalance,
|
||||
mudPointBalanceStatus,
|
||||
mudPointBalanceError,
|
||||
ownerUserId,
|
||||
mudPointBalance: visibleMudPointBalance,
|
||||
legacyWalletBalance: visibleLegacyWalletBalance,
|
||||
mudPointBalanceStatus: visibleMudPointBalanceStatus,
|
||||
mudPointBalanceError: visibleMudPointBalanceError,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
resetWalletBalance,
|
||||
walletLedgerOpen,
|
||||
walletLedgerOpen: walletUiIsVisible && walletLedgerOpen,
|
||||
setWalletLedgerOpen,
|
||||
walletLedger,
|
||||
walletLedgerLoading,
|
||||
walletLedgerError,
|
||||
rechargeOpen,
|
||||
walletLedger: walletUiIsVisible ? walletLedger : null,
|
||||
walletLedgerLoading: walletUiIsVisible && walletLedgerLoading,
|
||||
walletLedgerError: walletUiIsVisible ? walletLedgerError : null,
|
||||
rechargeOpen: walletUiIsVisible && rechargeOpen,
|
||||
rechargeModalCenter:
|
||||
rechargeContent && mudPointBalance
|
||||
rechargeContent && visibleWalletBalance !== null
|
||||
? {
|
||||
...rechargeContent,
|
||||
walletBalance: mudPointBalance.totalPoints,
|
||||
mudPointBalance,
|
||||
walletBalance: visibleWalletBalance,
|
||||
...(visibleMudPointBalance
|
||||
? { mudPointBalance: visibleMudPointBalance }
|
||||
: {}),
|
||||
}
|
||||
: null,
|
||||
rechargeLoading,
|
||||
rechargeError,
|
||||
submittingRechargeProductId,
|
||||
nativeRechargePayment,
|
||||
rechargeLoading: walletUiIsVisible && rechargeLoading,
|
||||
rechargeError: walletUiIsVisible ? rechargeError : null,
|
||||
submittingRechargeProductId: walletUiIsVisible
|
||||
? submittingRechargeProductId
|
||||
: null,
|
||||
nativeRechargePayment: walletUiIsVisible ? nativeRechargePayment : null,
|
||||
setNativeRechargePayment,
|
||||
loadWalletLedger,
|
||||
openWalletLedger,
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ import type {
|
||||
SetStateAction,
|
||||
UIEventHandler,
|
||||
} from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { GameCreationAppManifest } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import { PROJECT_SUPERVISOR_AGENT_ID } from '../../app/constants';
|
||||
@@ -1143,7 +1143,7 @@ export function SupervisorChatOnlyView({
|
||||
url.searchParams.set('genarrativeRevision', String(previewRevision));
|
||||
return url.toString();
|
||||
})();
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
setShowRuntimeDetails(false);
|
||||
}, [projectPath, runtime?.runId]);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -137,10 +137,10 @@ export function getClientProfileDashboard() {
|
||||
);
|
||||
}
|
||||
|
||||
export function getClientProfileRechargeCenter() {
|
||||
export function getClientProfileRechargeCenter(signal?: AbortSignal) {
|
||||
return requestClientApi<ProfileRechargeCenterResponse>(
|
||||
'/api/profile/recharge-center',
|
||||
{ method: 'GET' },
|
||||
{ method: 'GET', signal },
|
||||
'读取泥点明细失败',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,100 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
import type { ProfileMudPointBalance } from '../../../../packages/shared/src';
|
||||
import { createProfileWalletStore } from '../../../../packages/shared/src';
|
||||
import { getClientProfileRechargeCenter } from '../services/clientApi';
|
||||
|
||||
export type MudPointBalanceStatus = 'idle' | 'loading' | 'ready' | 'error';
|
||||
|
||||
type WalletStore = {
|
||||
mudPointBalance: ProfileMudPointBalance | null;
|
||||
mudPointBalanceStatus: MudPointBalanceStatus;
|
||||
mudPointBalanceError: string;
|
||||
applyWalletBalanceSnapshot: (balance: ProfileMudPointBalance) => void;
|
||||
onWalletBalanceMayHaveChanged: () => Promise<void>;
|
||||
resetWalletBalance: () => void;
|
||||
};
|
||||
|
||||
const initialWalletState: Pick<
|
||||
WalletStore,
|
||||
'mudPointBalance' | 'mudPointBalanceStatus' | 'mudPointBalanceError'
|
||||
> = {
|
||||
mudPointBalance: null,
|
||||
mudPointBalanceStatus: 'idle',
|
||||
mudPointBalanceError: '',
|
||||
};
|
||||
|
||||
let refreshVersion = 0;
|
||||
let requestGeneration = 0;
|
||||
let activeRefresh: Promise<void> | null = null;
|
||||
|
||||
export const useWalletStore = create<WalletStore>((set) => ({
|
||||
...initialWalletState,
|
||||
applyWalletBalanceSnapshot: (balance) => {
|
||||
requestGeneration += 1;
|
||||
refreshVersion = 0;
|
||||
activeRefresh = null;
|
||||
set({
|
||||
mudPointBalance: balance,
|
||||
mudPointBalanceStatus: 'ready',
|
||||
mudPointBalanceError: '',
|
||||
});
|
||||
},
|
||||
onWalletBalanceMayHaveChanged: () => {
|
||||
refreshVersion += 1;
|
||||
if (activeRefresh) {
|
||||
return activeRefresh;
|
||||
}
|
||||
|
||||
const refreshRequestGeneration = requestGeneration;
|
||||
const refresh = (async () => {
|
||||
while (refreshRequestGeneration === requestGeneration) {
|
||||
const requestedVersion = refreshVersion;
|
||||
set({ mudPointBalanceStatus: 'loading', mudPointBalanceError: '' });
|
||||
try {
|
||||
const center = await getClientProfileRechargeCenter();
|
||||
if (!center.mudPointBalance) {
|
||||
throw new Error('充值中心响应缺少泥点余额');
|
||||
}
|
||||
if (refreshRequestGeneration !== requestGeneration) {
|
||||
return;
|
||||
}
|
||||
if (requestedVersion !== refreshVersion) {
|
||||
continue;
|
||||
}
|
||||
set({
|
||||
mudPointBalance: center.mudPointBalance,
|
||||
mudPointBalanceStatus: 'ready',
|
||||
mudPointBalanceError: '',
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
if (refreshRequestGeneration !== requestGeneration) {
|
||||
return;
|
||||
}
|
||||
if (requestedVersion !== refreshVersion) {
|
||||
continue;
|
||||
}
|
||||
set({
|
||||
mudPointBalanceStatus: 'error',
|
||||
mudPointBalanceError:
|
||||
error instanceof Error ? error.message : '泥点明细读取失败',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
})();
|
||||
activeRefresh = refresh;
|
||||
void refresh.finally(() => {
|
||||
if (activeRefresh === refresh) {
|
||||
activeRefresh = null;
|
||||
}
|
||||
});
|
||||
return refresh;
|
||||
},
|
||||
resetWalletBalance: () => {
|
||||
requestGeneration += 1;
|
||||
refreshVersion = 0;
|
||||
activeRefresh = null;
|
||||
set(initialWalletState);
|
||||
},
|
||||
}));
|
||||
export const useWalletStore = createProfileWalletStore({
|
||||
getRechargeCenter: getClientProfileRechargeCenter,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user