存量前端:生图分组/灯箱/重生控件/导入文案/驳回横幅+SSE+总核销表
- ImageStrategyGroup/ImageLightbox/RegenControls/ImportTextModal/ RejectReasonBanner 新建组件 - tasks images/text/new + history + 配置/布局/SSE hooks 存量改动 - 总核销表进度更新 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { FlywheelBanner } from '@/components/FlywheelBanner';
|
||||
import { ConfigPreviewPanel } from '@/components/tasks/ConfigPreviewPanel';
|
||||
import { RecentTasksBar } from '@/components/tasks/RecentTasksBar';
|
||||
import { NewTaskForm } from '@/components/tasks/NewTaskForm';
|
||||
import { ImportTextModal } from '@/components/tasks/ImportTextModal';
|
||||
import { api } from '@/lib/api';
|
||||
import { Product, CreateTaskRequest } from '@/types/index';
|
||||
import { PaginatedResponse } from '@/types/index';
|
||||
@@ -32,6 +33,7 @@ export default function NewTaskPage() {
|
||||
});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [importOpen, setImportOpen] = useState(false);
|
||||
|
||||
useEffect(() => { loadProducts(); }, []);
|
||||
|
||||
@@ -45,17 +47,22 @@ export default function NewTaskPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.FormEvent, track: 'ai' | 'import') {
|
||||
e.preventDefault();
|
||||
if (!selectedProduct) { setError('请选择产品'); return; }
|
||||
if (!form.theme.trim()) { setError('请填写今天主题'); return; }
|
||||
function validate(): boolean {
|
||||
if (!selectedProduct) { setError('请选择产品'); return false; }
|
||||
if (!form.theme.trim()) { setError('请填写今天主题'); return false; }
|
||||
// 禁降级:产品入镜但该产品没传参考图 → 拦在前端,引导去上传
|
||||
if (form.need_product_image && !selectedProduct.image_path) {
|
||||
setError('该产品还没上传参考图,无法生成产品入镜内容。请先到「配置-产品库」上传产品图,或关闭下方「产品入镜」开关。');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
setSubmitting(true);
|
||||
setError('');
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.FormEvent, track: 'ai' | 'import') {
|
||||
e.preventDefault();
|
||||
if (!validate() || !selectedProduct) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const req: CreateTaskRequest = { product_id: selectedProduct.id, ...form, track };
|
||||
const data = await api.post<{ id: number }>('/api/v1/tasks', req);
|
||||
@@ -68,6 +75,43 @@ export default function NewTaskPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// 轨B:先校验产品/主题,通过后开弹窗让用户贴文案
|
||||
function openImport(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (validate()) setImportOpen(true);
|
||||
}
|
||||
|
||||
// 弹窗确认:建 import 任务 → 逐条导入候选池 → 跳 text 页
|
||||
async function handleImportConfirm(contents: string[]) {
|
||||
if (!selectedProduct) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const req: CreateTaskRequest = { product_id: selectedProduct.id, ...form, track: 'import' };
|
||||
const data = await api.post<{ id: number }>('/api/v1/tasks', req);
|
||||
// 任务已建。逐条导入;某条失败也不丢任务——跳进已建任务的 text 页,
|
||||
// 半成品文案在那里可见可续,避免任务 ID 丢失变成找不回的孤儿任务。
|
||||
let imported = 0;
|
||||
try {
|
||||
for (const content of contents) {
|
||||
await api.post(`/api/v1/tasks/${data.id}/import-text`, { content });
|
||||
imported += 1;
|
||||
}
|
||||
} catch (impErr) {
|
||||
const ae = impErr as ApiError;
|
||||
setError(`已导入 ${imported}/${contents.length} 条后中断(${ae?.message || '导入失败'}),已为你保留任务 #${data.id},可在文案页补齐。`);
|
||||
}
|
||||
setImportOpen(false);
|
||||
router.push(`/tasks/${data.id}/text`);
|
||||
} catch (err) {
|
||||
// 建任务本身失败(还没有任务 ID)
|
||||
const apiErr = err as ApiError;
|
||||
setError(apiErr?.message || getErrorAction(apiErr?.code).message);
|
||||
setImportOpen(false);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<ProgressBar currentStep={1} />
|
||||
@@ -86,7 +130,7 @@ export default function NewTaskPage() {
|
||||
submitting={submitting}
|
||||
error={error}
|
||||
onSubmitAi={(e) => handleSubmit(e, 'ai')}
|
||||
onSubmitImport={(e) => handleSubmit(e, 'import')}
|
||||
onSubmitImport={openImport}
|
||||
/>
|
||||
<div className="mt-8"><RecentTasksBar /></div>
|
||||
</div>
|
||||
@@ -94,6 +138,13 @@ export default function NewTaskPage() {
|
||||
{/* 右侧配置预览面板 */}
|
||||
{selectedProduct && <ConfigPreviewPanel product={selectedProduct} />}
|
||||
</div>
|
||||
|
||||
<ImportTextModal
|
||||
open={importOpen}
|
||||
submitting={submitting}
|
||||
onClose={() => setImportOpen(false)}
|
||||
onConfirm={handleImportConfirm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user