fix(frontend): keep browser API on same-origin rewrites in dev

Document that NEXT_PUBLIC_WAVELET_BACKEND_URL must stay unset for local
next dev so axios hits /api/* and Next rewrites proxy to the backend.
This commit is contained in:
ryan
2026-07-24 16:37:24 +08:00
parent a4588b05e0
commit 9f15f42b47
75 changed files with 5971 additions and 2450 deletions
+6 -4
View File
@@ -16,6 +16,7 @@ import type {
} from '@/lib/services/admin';
import services from '@/lib/services';
import { handleContextError } from '@/lib/utils/error-handling';
import { useTranslations } from 'next-intl';
/** Admin 上下文状态接口 */
export interface AdminContextState {
@@ -45,6 +46,7 @@ const AdminContext = createContext<AdminContextState | null>(null);
*/
export function AdminProvider({ children }: { children: React.ReactNode }) {
const queryClient = useQueryClient();
const t = useTranslations('contexts.admin');
const [systemConfigs, setSystemConfigs] = useState<SystemConfig[]>([]);
const [systemConfigsLoading, setSystemConfigsLoading] = useState(false);
const [systemConfigsError, setSystemConfigsError] = useState<Error | null>(
@@ -78,14 +80,14 @@ export function AdminProvider({ children }: { children: React.ReactNode }) {
return;
}
const errorObject = handleContextError(error, '加载系统配置失败', {
const errorObject = handleContextError(error, t('loadConfigFailed'), {
logError: true,
});
setSystemConfigsError(errorObject);
setSystemConfigsLoading(false);
}
},
[],
[t],
);
/** 更新系统配置 */
@@ -96,11 +98,11 @@ export function AdminProvider({ children }: { children: React.ReactNode }) {
await queryClient.invalidateQueries({ queryKey: ['public-config'] });
await refetchSystemConfigs(lastConfigTypeRef.current);
} catch (error) {
handleContextError(error, '更新系统配置失败');
handleContextError(error, t('updateConfigFailed'));
throw error;
}
},
[queryClient, refetchSystemConfigs],
[queryClient, refetchSystemConfigs, t],
);
const value: AdminContextState = {
+13 -8
View File
@@ -17,6 +17,7 @@ import type {
UpdateUserRequest,
} from '@/lib/services/admin';
import services from '@/lib/services';
import { useTranslations } from 'next-intl';
/** 用户列表查询参数 */
export interface UserQueryParams {
@@ -69,6 +70,7 @@ export function AdminUsersProvider({
}: {
children: React.ReactNode;
}) {
const t = useTranslations('contexts.adminUsers');
// State
const [users, setUsers] = useState<AdminUser[]>([]);
const [total, setTotal] = useState(0);
@@ -237,7 +239,10 @@ export function AdminUsersProvider({
is_active: !user.is_active,
});
toast.success(
`已${!user.is_active ? '启用' : '禁用'}用户 ${user.username}`,
t('userStatusChanged', {
action: !user.is_active ? t('enabled') : t('disabled'),
username: user.username,
}),
);
} catch {
// Revert on error
@@ -246,7 +251,7 @@ export function AdminUsersProvider({
u.id === user.id ? { ...u, is_active: originalStatus } : u,
),
);
toast.error('更新状态失败');
toast.error(t('updateStatusFailed'));
}
};
@@ -261,10 +266,10 @@ export function AdminUsersProvider({
setTotal((prev) => prev + 1);
// Clear cache because data changed
cacheRef.current = {};
toast.success(`已成功创建用户 ${newUser.username}`);
toast.success(t('userCreated', { username: newUser.username }));
return newUser;
} catch (err) {
toast.error(err instanceof Error ? err.message : '创建用户失败');
toast.error(err instanceof Error ? err.message : t('createUserFailed'));
throw err;
}
};
@@ -275,9 +280,9 @@ export function AdminUsersProvider({
setUsers((prev) => prev.filter((u) => u.id !== user.id));
setTotal((prev) => Math.max(0, prev - 1));
cacheRef.current = {};
toast.success(`已删除用户 ${user.username}`);
toast.success(t('userDeleted', { username: user.username }));
} catch (err) {
toast.error(err instanceof Error ? err.message : '删除用户失败');
toast.error(err instanceof Error ? err.message : t('deleteUserFailed'));
throw err;
}
};
@@ -287,9 +292,9 @@ export function AdminUsersProvider({
await services.adminUser.updateUser(id, req);
setUsers((prev) => prev.map((u) => (u.id === id ? { ...u, ...req } : u)));
cacheRef.current = {};
toast.success('更新用户信息成功');
toast.success(t('updateUserSuccess'));
} catch (err) {
toast.error(err instanceof Error ? err.message : '更新用户信息失败');
toast.error(err instanceof Error ? err.message : t('updateUserFailed'));
throw err;
}
};
+7 -4
View File
@@ -12,6 +12,7 @@ import {
import { AuthService } from '@/lib/services/auth';
import { User } from '@/lib/services/auth/types';
import { useTranslations } from 'next-intl';
/** 用户状态接口 */
interface UserState {
@@ -52,6 +53,7 @@ export function UserProvider({ children }: { children: ReactNode }) {
});
const isMountedRef = useRef(true);
const t = useTranslations('contexts.user');
/** 获取用户信息 */
const fetchUser = useCallback(async () => {
@@ -68,10 +70,10 @@ export function UserProvider({ children }: { children: ReactNode }) {
setState({
user: null,
loading: false,
error: error instanceof Error ? error.message : '获取用户信息失败',
error: error instanceof Error ? error.message : t('getUserInfoFailed'),
});
}
}, []);
}, [t]);
/** 重新获取用户信息 */
const refetch = useCallback(async () => {
@@ -97,14 +99,15 @@ export function UserProvider({ children }: { children: ReactNode }) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : '登出失败';
const errorMessage =
error instanceof Error ? error.message : t('logoutFailed');
setState((prev) => ({
...prev,
error: errorMessage,
}));
throw new Error(errorMessage);
}
}, []);
}, [t]);
/** 组件挂载时获取用户信息(登录/注册页跳过,避免无意义请求) */
useEffect(() => {