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
@@ -1,6 +1,7 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -166,6 +167,7 @@ function DatePickerWithTime({
}
export function TaskManager() {
const t = useTranslations('admin.tasks');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [taskTypes, setTaskTypes] = useState<TaskMeta[]>([]);
@@ -186,11 +188,13 @@ export function TaskManager() {
const data = await services.adminTask.getTaskTypes();
setTaskTypes(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('加载任务类型失败'));
setError(
err instanceof Error ? err : new Error(t('loadTaskTypesFailed')),
);
} finally {
setLoading(false);
}
}, []);
}, [t]);
useEffect(() => {
fetchTaskTypes();
@@ -249,8 +253,11 @@ export function TaskManager() {
const taskID = await services.adminTask.dispatchTask(params);
toast.success('任务下发成功', {
description: `已成功将任务 ${targetTask?.name || selectedTaskType} 加入队列:${taskID}`,
toast.success(t('dispatchSuccess'), {
description: t('dispatchSuccessDesc', {
name: targetTask?.name || selectedTaskType,
taskId: taskID,
}),
});
setDialogOpen(false);
@@ -259,8 +266,8 @@ export function TaskManager() {
setUserId('');
setParamValues({});
} catch (err) {
toast.error('任务下发失败', {
description: err instanceof Error ? err.message : '未知错误',
toast.error(t('dispatchFailed'), {
description: err instanceof Error ? err.message : t('unknownError'),
});
} finally {
setDispatching(false);
@@ -292,10 +299,10 @@ export function TaskManager() {
) : loading && taskTypes.length === 0 ? (
<LoadingStateWithBorder
icon={Layers}
description='加载任务类型中...'
description={t('loadingTaskTypes')}
/>
) : taskTypes.length === 0 ? (
<EmptyStateWithBorder icon={Layers} description='暂无可用任务类型' />
<EmptyStateWithBorder icon={Layers} description={t('noTaskTypes')} />
) : (
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4'>
{taskTypes.map((task, index) => {
@@ -333,13 +340,15 @@ export function TaskManager() {
variant='outline'
className='text-[9px] h-4.5 bg-background/50 font-mono text-muted-foreground border-border/50 px-1'
>
类型:{task.type}
{t('taskType')}
{task.type}
</Badge>
<Badge
variant='outline'
className='text-[9px] h-4.5 bg-background/50 font-mono text-muted-foreground border-border/50 px-1'
>
重试:{task.max_retry}
{t('taskRetry')}
{task.max_retry}
</Badge>
</div>
</div>
@@ -351,7 +360,7 @@ export function TaskManager() {
onClick={() => openDispatchDialog(task.type)}
>
<Play className='size-3 mr-1' />
立即执行
{t('executeNow')}
</Button>
</div>
</div>
@@ -365,15 +374,13 @@ export function TaskManager() {
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className='sm:max-w-[500px]'>
<DialogHeader>
<DialogTitle>下发任务</DialogTitle>
<DialogDescription>
配置任务参数并启动,任务将在后台异步执行。
</DialogDescription>
<DialogTitle>{t('dispatchTask')}</DialogTitle>
<DialogDescription>{t('dispatchTaskDesc')}</DialogDescription>
</DialogHeader>
<div className='grid gap-4 py-4'>
<div className='grid gap-2'>
<Label>执行任务</Label>
<Label>{t('executeTask')}</Label>
<div className='flex items-center gap-2 p-2 rounded-lg bg-muted/50'>
{(() => {
const meta = getSelectedTaskMeta();
@@ -403,20 +410,20 @@ export function TaskManager() {
return (
<>
<div className='grid gap-2'>
<Label>开始时间</Label>
<Label>{t('startTime')}</Label>
<DatePickerWithTime
date={startTime}
setDate={setStartTime}
/>
<p className='text-xs text-muted-foreground'>
选择同步的起始时间点
{t('startTimeDesc')}
</p>
</div>
<div className='grid gap-2'>
<Label>结束时间</Label>
<Label>{t('endTime')}</Label>
<DatePickerWithTime date={endTime} setDate={setEndTime} />
<p className='text-xs text-muted-foreground'>
选择同步的结束时间点
{t('endTimeDesc')}
</p>
</div>
</>
@@ -474,8 +481,8 @@ export function TaskManager() {
/>
<span className='text-xs text-muted-foreground'>
{paramValues[param.name] === 'true'
? '开启'
: '关闭'}
? t('paramOn')
: t('paramOff')}
</span>
</div>
) : (
@@ -513,7 +520,7 @@ export function TaskManager() {
return (
<div className='flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 p-3 rounded-md border border-dashed'>
<Info className='h-4 w-4' />
<span>此任务无需额外配置参数,确认后将直接下发。</span>
<span>{t('noParamsHint')}</span>
</div>
);
}
@@ -528,7 +535,7 @@ export function TaskManager() {
disabled={dispatching}
className='h-8 text-xs'
>
取消
{t('cancel')}
</Button>
<Button
onClick={handleDispatch}
@@ -540,7 +547,7 @@ export function TaskManager() {
) : (
<Play className='size-3' />
)}
{dispatching ? '下发中' : '启动'}
{dispatching ? t('dispatching') : t('start')}
</Button>
</DialogFooter>
</DialogContent>