baseline: Clover 独立仓库首次基线提交

将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
'use client';
/**
* 屏2 开新任务(熟手 /tasks/new
* 使用 NewTaskForm + ConfigPreviewPanel + RecentTasksBar + FlywheelBanner
*/
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { ProgressBar } from '@/components/layout/ProgressBar';
import { FlywheelBanner } from '@/components/FlywheelBanner';
import { ConfigPreviewPanel } from '@/components/tasks/ConfigPreviewPanel';
import { RecentTasksBar } from '@/components/tasks/RecentTasksBar';
import { NewTaskForm } from '@/components/tasks/NewTaskForm';
import { api } from '@/lib/api';
import { Product, CreateTaskRequest } from '@/types/index';
import { PaginatedResponse } from '@/types/index';
import { usePreferenceStore } from '@/stores/preferenceStore';
import { getErrorAction } from '@/types/errors';
import { ApiError } from '@/types/index';
export default function NewTaskPage() {
const router = useRouter();
const { context } = usePreferenceStore();
const [products, setProducts] = useState<Product[]>([]);
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
const [form, setForm] = useState<Omit<CreateTaskRequest, 'product_id'>>({
benchmark_ids: [],
theme: '',
text_count: 8,
image_count: 6,
track: 'ai',
need_product_image: true,
});
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState('');
useEffect(() => { loadProducts(); }, []);
async function loadProducts() {
try {
const data = await api.get<PaginatedResponse<Product>>('/api/v1/products');
setProducts(data.items);
if (data.items.length > 0) setSelectedProduct(data.items[0]);
} catch {
// ignore
}
}
async function handleSubmit(e: React.FormEvent, track: 'ai' | 'import') {
e.preventDefault();
if (!selectedProduct) { setError('请选择产品'); return; }
if (!form.theme.trim()) { setError('请填写今天主题'); return; }
// 禁降级:产品入镜但该产品没传参考图 → 拦在前端,引导去上传
if (form.need_product_image && !selectedProduct.image_path) {
setError('该产品还没上传参考图,无法生成产品入镜内容。请先到「配置-产品库」上传产品图,或关闭下方「产品入镜」开关。');
return;
}
setSubmitting(true);
setError('');
try {
const req: CreateTaskRequest = { product_id: selectedProduct.id, ...form, track };
const data = await api.post<{ id: number }>('/api/v1/tasks', req);
router.push(`/tasks/${data.id}/text`);
} catch (err) {
const apiErr = err as ApiError;
setError(apiErr?.message || getErrorAction(apiErr?.code).message);
} finally {
setSubmitting(false);
}
}
return (
<div className="flex flex-col h-full">
<ProgressBar currentStep={1} />
{context && <FlywheelBanner context={context} />}
<div className="flex flex-1 overflow-hidden">
{/* 主表单区 */}
<div className="flex-1 p-6 overflow-auto">
<h2 className="text-xl font-bold text-text-primary mb-6"></h2>
<NewTaskForm
products={products}
selectedProduct={selectedProduct}
onSelectProduct={setSelectedProduct}
form={form}
onFormChange={(patch) => setForm((f) => ({ ...f, ...patch }))}
submitting={submitting}
error={error}
onSubmitAi={(e) => handleSubmit(e, 'ai')}
onSubmitImport={(e) => handleSubmit(e, 'import')}
/>
<div className="mt-8"><RecentTasksBar /></div>
</div>
{/* 右侧配置预览面板 */}
{selectedProduct && <ConfigPreviewPanel product={selectedProduct} />}
</div>
</div>
);
}