合并主分支
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Successful in 3m1s
Project CI / Native shell tests (pull_request) Successful in 13m12s

同步主分支资源卡依赖关系与类型分类预览等最新变更
保留并补齐画布快速编辑前后端正向白名单
拒绝图片编辑接口处理单个图标、角色动作、音频、视频及未知类型
修正快速编辑决策记录与现行编辑器文档
补充后端现役类型和未知类型表驱动测试
This commit is contained in:
2026-08-05 20:14:45 +08:00
76 changed files with 9334 additions and 1261 deletions
@@ -538,6 +538,9 @@ export function ImageCanvasEditorView({
currentUser: authUi?.user ?? null,
});
const refreshEditorWalletState = useCallback(() => {
if (!authUiRef.current?.canAccessProtectedData || !authUiRef.current.user) {
return;
}
refreshEditorWalletBalance();
loadRechargeCenter();
}, [loadRechargeCenter, refreshEditorWalletBalance]);
@@ -0,0 +1,82 @@
/* @vitest-environment jsdom */
import { act, renderHook } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { ProfileRechargeCenterResponse } from '../../../packages/shared/src/contracts/runtime';
import { usePlatformProfileCenterController } from './usePlatformProfileCenterController';
const getPlatformProfileRechargeCenterMock = vi.hoisted(() => vi.fn());
vi.mock('../../services/platform-entry/platformProfileClient', async () => {
const actual = await vi.importActual<
typeof import('../../services/platform-entry/platformProfileClient')
>('../../services/platform-entry/platformProfileClient');
return {
...actual,
getPlatformProfileRechargeCenter: getPlatformProfileRechargeCenterMock,
};
});
const rechargeCenter: ProfileRechargeCenterResponse = {
walletBalance: 100,
mudPointBalance: {
totalPoints: 100,
permanentPoints: 100,
limitedPoints: 0,
limitedExpiresAt: null,
dailyFreePoints: 0,
dailyFreeResetPoints: 0,
dailyFreeResetsAt: null,
},
membership: {
status: 'normal',
tier: 'normal',
startedAt: null,
expiresAt: null,
updatedAt: null,
cycleStartedAt: null,
cycleResetsAt: null,
cycleGrantedPoints: 0,
cycleRemainingPoints: 0,
cyclePeriodDays: 30,
},
pointProducts: [],
membershipProducts: [],
benefits: [],
latestOrder: null,
hasPointsRecharged: false,
};
describe('usePlatformProfileCenterController', () => {
afterEach(() => {
getPlatformProfileRechargeCenterMock.mockReset();
});
it('aborts an unfinished recharge center read when the consumer unmounts', async () => {
let resolveRechargeCenter!: (center: ProfileRechargeCenterResponse) => void;
const pendingRead = new Promise<ProfileRechargeCenterResponse>((resolve) => {
resolveRechargeCenter = resolve;
});
getPlatformProfileRechargeCenterMock.mockReturnValue(pendingRead);
const { result, unmount } = renderHook(() =>
usePlatformProfileCenterController({
activeTab: 'editor-canvas',
isAuthenticated: false,
showRechargeEntry: true,
requestLogin: vi.fn(),
currentUser: null,
}),
);
act(() => result.current.loadRechargeCenter());
const requestOptions = getPlatformProfileRechargeCenterMock.mock.calls[0]?.[0];
expect(requestOptions?.signal.aborted).toBe(false);
unmount();
expect(requestOptions?.signal.aborted).toBe(true);
resolveRechargeCenter(rechargeCenter);
await pendingRead;
});
});
@@ -354,6 +354,16 @@ export function usePlatformProfileCenterController({
const pendingWechatRechargeOrderIdRef = useRef<string | null>(null);
const confirmingWechatRechargeOrderIdRef = useRef<string | null>(null);
const rechargeCenterReadRevisionRef = useRef(0);
const rechargeCenterReadAbortControllerRef =
useRef<AbortController | null>(null);
useEffect(() => {
return () => {
rechargeCenterReadRevisionRef.current += 1;
rechargeCenterReadAbortControllerRef.current?.abort();
rechargeCenterReadAbortControllerRef.current = null;
};
}, []);
// 中文注释:支持带邀请码 query 的直达场景,登录成功后自动打开兑换面板并复用同一套输入状态。
useEffect(() => {
@@ -398,6 +408,8 @@ export function usePlatformProfileCenterController({
const applyRechargeCenter = useCallback(
(center: ProfileRechargeCenterResponse) => {
rechargeCenterReadRevisionRef.current += 1;
rechargeCenterReadAbortControllerRef.current?.abort();
rechargeCenterReadAbortControllerRef.current = null;
setIsLoadingRechargeCenter(false);
setRechargeError(null);
setRechargeCenter(center);
@@ -407,16 +419,25 @@ export function usePlatformProfileCenterController({
const loadRechargeCenter = useCallback(() => {
const revision = ++rechargeCenterReadRevisionRef.current;
rechargeCenterReadAbortControllerRef.current?.abort();
const abortController = new AbortController();
rechargeCenterReadAbortControllerRef.current = abortController;
setRechargeError(null);
setIsLoadingRechargeCenter(true);
void getPlatformProfileRechargeCenter()
void getPlatformProfileRechargeCenter({ signal: abortController.signal })
.then((center) => {
if (revision === rechargeCenterReadRevisionRef.current) {
if (
!abortController.signal.aborted &&
revision === rechargeCenterReadRevisionRef.current
) {
setRechargeCenter(center);
}
})
.catch((error: unknown) => {
if (revision !== rechargeCenterReadRevisionRef.current) {
if (
abortController.signal.aborted ||
revision !== rechargeCenterReadRevisionRef.current
) {
return;
}
setRechargeCenter(null);
@@ -425,6 +446,9 @@ export function usePlatformProfileCenterController({
);
})
.finally(() => {
if (rechargeCenterReadAbortControllerRef.current === abortController) {
rechargeCenterReadAbortControllerRef.current = null;
}
if (revision === rechargeCenterReadRevisionRef.current) {
setIsLoadingRechargeCenter(false);
}