Files
gdm-Servo-model/app/page.tsx
2026-08-10 14:22:47 +08:00

947 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { FormEvent, useMemo, useState } from "react";
import {
CATALOG_COUNTS,
CATALOG_PRODUCTS,
type CatalogProduct,
} from "./catalog-data";
type Gear = "塑料齿" | "半金属齿" | "金属齿";
type Product = {
id: string;
name: string;
subtitle: string;
weight: number;
torque: number;
speed: number;
voltageMin: number;
voltageMax: number;
gear: Gear;
price: number;
size: string;
image: string;
applications: string[];
code: string;
};
type Requirements = {
application: string;
torque: number;
voltage: number;
weightLimit: number;
gear: "不限" | Gear;
annualVolume: string;
currentModel: string;
painPoint: string;
};
const PRODUCTS: Product[] = [
{
id: "17g",
name: "1.7克超微型舵机",
subtitle: "极致轻量,适合微型结构",
weight: 1.7,
torque: 0.09,
speed: 0.12,
voltageMin: 2.7,
voltageMax: 5,
gear: "塑料齿",
price: 30,
size: "13.40 × 6.15 × 16.00 mm",
image: "/products/servo-17g.png",
applications: ["AI毛绒", "智能玩具", "微型机器人", "云台/航模"],
code: "4.A.01.A0060",
},
{
id: "2g",
name: "2克塑料齿舵机",
subtitle: "快速响应的微型执行方案",
weight: 2,
torque: 0.4,
speed: 0.08,
voltageMin: 3.5,
voltageMax: 6,
gear: "塑料齿",
price: 14.4,
size: "16.25 × 8.30 × 16.90 mm",
image: "/products/servo-2g.jpg",
applications: ["AI毛绒", "智能玩具", "微型机器人", "云台/航模"],
code: "4.A.01.A0004",
},
{
id: "37g",
name: "3.7克塑料齿舵机",
subtitle: "轻量玩具与科创教育通用",
weight: 3.7,
torque: 0.8,
speed: 0.09,
voltageMin: 3.5,
voltageMax: 6,
gear: "塑料齿",
price: 14.7,
size: "20.40 × 8.70 × 20.32 mm",
image: "/products/servo-37g.jpg",
applications: ["AI毛绒", "智能玩具", "教育/积木", "云台/航模"],
code: "4.A.01.A0003",
},
{
id: "9p",
name: "9克塑料齿舵机",
subtitle: "成本优先的标准轻载方案",
weight: 9,
torque: 2.1,
speed: 0.11,
voltageMin: 4.8,
voltageMax: 6,
gear: "塑料齿",
price: 13.1,
size: "23.00 × 12.36 × 28.50 mm",
image: "/products/servo-9g-plastic.jpg",
applications: ["智能玩具", "教育/积木", "微型机器人", "云台/航模"],
code: "4.A.01.A0009",
},
{
id: "9h",
name: "9克半金属齿舵机",
subtitle: "耐久与成本兼顾",
weight: 9,
torque: 3.2,
speed: 0.13,
voltageMin: 4.8,
voltageMax: 6,
gear: "半金属齿",
price: 18,
size: "23.00 × 12.36 × 28.50 mm",
image: "/products/servo-9g-hybrid.jpg",
applications: ["智能玩具", "教育/积木", "微型机器人", "云台/航模"],
code: "4.A.01.B0001",
},
{
id: "9m",
name: "9克全金属齿舵机",
subtitle: "高频使用与冲击负载优选",
weight: 9,
torque: 3.2,
speed: 0.13,
voltageMin: 4.8,
voltageMax: 6,
gear: "金属齿",
price: 28.7,
size: "23.00 × 12.36 × 28.50 mm",
image: "/products/servo-9g-metal.jpg",
applications: ["教育/积木", "微型机器人", "云台/航模", "工业设备"],
code: "4.A.01.B0026",
},
{
id: "17p",
name: "17克塑料齿舵机",
subtitle: "玩具机构的高性价比选择",
weight: 17,
torque: 5,
speed: 0.1,
voltageMin: 4.8,
voltageMax: 6,
gear: "塑料齿",
price: 14.9,
size: "28.60 × 13.00 × 31.00 mm",
image: "/products/servo-17g-plastic.jpg",
applications: ["智能玩具", "教育/积木", "微型机器人"],
code: "4.A.01.A0002",
},
{
id: "17m",
name: "17克金属齿舵机",
subtitle: "宽电压、高扭矩、耐冲击",
weight: 17,
torque: 8.8,
speed: 0.18,
voltageMin: 4.8,
voltageMax: 8.4,
gear: "金属齿",
price: 27.8,
size: "28.60 × 13.00 × 31.00 mm",
image: "/products/servo-17g-metal.jpg",
applications: ["智能玩具", "微型机器人", "云台/航模", "工业设备"],
code: "4.A.01.B0007",
},
{
id: "25p",
name: "25克塑料齿舵机",
subtitle: "中等负载的经济型方案",
weight: 25,
torque: 7.5,
speed: 0.15,
voltageMin: 4.8,
voltageMax: 6,
gear: "塑料齿",
price: 16.4,
size: "36.00 × 15.20 × 29.50 mm",
image: "/products/servo-25g-plastic.jpg",
applications: ["智能玩具", "教育/积木", "微型机器人"],
code: "4.A.01.A0001",
},
{
id: "25m",
name: "25克金属齿舵机",
subtitle: "中型机器人和高频机构",
weight: 25,
torque: 8.2,
speed: 0.15,
voltageMin: 4.8,
voltageMax: 8.4,
gear: "金属齿",
price: 44.7,
size: "36.00 × 15.20 × 29.50 mm",
image: "/products/servo-25g-metal.jpg",
applications: ["微型机器人", "云台/航模", "工业设备"],
code: "4.A.01.B0014",
},
{
id: "37p",
name: "37克塑料齿舵机",
subtitle: "标准尺寸、大负载经济型",
weight: 37,
torque: 6.8,
speed: 0.22,
voltageMin: 4.8,
voltageMax: 6,
gear: "塑料齿",
price: 18.4,
size: "40.85 × 20.15 × 39.15 mm",
image: "/products/servo-37g-plastic.jpg",
applications: ["智能玩具", "微型机器人", "工业设备"],
code: "4.A.01.B0015",
},
{
id: "37h",
name: "37克半金属齿舵机",
subtitle: "大扭矩与成本的平衡点",
weight: 37,
torque: 11.2,
speed: 0.26,
voltageMin: 4.8,
voltageMax: 6,
gear: "半金属齿",
price: 32.9,
size: "40.85 × 20.15 × 39.15 mm",
image: "/products/servo-37g-hybrid.jpg",
applications: ["微型机器人", "云台/航模", "工业设备"],
code: "4.A.01.B0005",
},
{
id: "37m",
name: "37克金属齿舵机",
subtitle: "24kgf·cm级强劲输出",
weight: 37,
torque: 24,
speed: 0.22,
voltageMin: 4.8,
voltageMax: 8.4,
gear: "金属齿",
price: 37.6,
size: "40.85 × 20.15 × 39.15 mm",
image: "/products/servo-37g-metal.jpg",
applications: ["机器人/机械臂", "云台/航模", "工业设备"],
code: "4.A.01.B0017",
},
];
const APPLICATIONS = [
{ name: "AI毛绒", note: "头部、耳朵、手臂等轻量动作" },
{ name: "智能玩具", note: "互动机构、表情和行走结构" },
{ name: "教育/积木", note: "STEAM套件与积木动力模块" },
{ name: "微型机器人", note: "关节、夹爪和小型机械臂" },
{ name: "云台/航模", note: "飞行控制、云台与模型机构" },
{ name: "工业设备", note: "控制机构与定制传动组件" },
];
const CATALOG_CATEGORIES = [
"全部产品",
"标准舵机",
"异形舵机",
"全金属舵机",
"大扭矩舵机",
"积木舵机",
"标准齿轮箱",
"翻盖电机",
];
const FEATURED_SPEC_ORDER = [
"堵转扭矩",
"额定电压",
"电压范围",
"信号类型",
"通讯方式",
"齿轮比",
"寿命",
"产品尺寸",
"外形尺寸",
];
function getFeaturedSpecs(product: CatalogProduct) {
return FEATURED_SPEC_ORDER
.filter((label) => product.specs[label])
.slice(0, 3)
.map((label) => [label, product.specs[label]] as const);
}
const DEFAULT_REQUIREMENTS: Requirements = {
application: "",
torque: 2,
voltage: 5,
weightLimit: 40,
gear: "不限",
annualVolume: "500050000件",
currentModel: "",
painPoint: "交期",
};
function getMatches(requirements: Requirements) {
return PRODUCTS.map((product) => {
let score = 34;
const reasons: string[] = [];
if (product.applications.includes(requirements.application)) {
score += 18;
reasons.push(`适合${requirements.application}场景`);
}
if (product.torque >= requirements.torque) {
score += 25;
const margin = Math.round((product.torque / requirements.torque - 1) * 100);
reasons.push(`扭矩满足,约有${Math.max(0, margin)}%余量`);
} else {
score -= 70;
}
if (
requirements.voltage >= product.voltageMin &&
requirements.voltage <= product.voltageMax
) {
score += 14;
reasons.push(`${requirements.voltage}V供电兼容`);
} else {
score -= 35;
}
if (product.weight <= requirements.weightLimit) {
score += 10;
reasons.push(`重量不超过${requirements.weightLimit}g`);
} else {
score -= 45;
}
if (requirements.gear === "不限" || requirements.gear === product.gear) {
score += requirements.gear === "不限" ? 4 : 12;
if (requirements.gear !== "不限") reasons.push(`${product.gear}符合偏好`);
} else if (requirements.gear === "金属齿" && product.gear !== "金属齿") {
score -= 22;
}
if (requirements.painPoint === "成本" && product.gear === "塑料齿") score += 8;
if (requirements.painPoint === "耐久" && product.gear === "金属齿") score += 8;
if (requirements.painPoint === "体积" && product.weight <= 9) score += 8;
return {
product,
score,
confidence: Math.max(52, Math.min(96, score)),
reasons: reasons.slice(0, 3),
};
}).sort((a, b) => b.score - a.score);
}
function Metric({ label, value }: { label: string; value: string }) {
return (
<div className="metric">
<span>{label}</span>
<strong>{value}</strong>
</div>
);
}
export default function Home() {
const [stage, setStage] = useState(0);
const [requirements, setRequirements] = useState(DEFAULT_REQUIREMENTS);
const [showSampleForm, setShowSampleForm] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [leadId, setLeadId] = useState("");
const [catalogQuery, setCatalogQuery] = useState("");
const [catalogCategory, setCatalogCategory] = useState("全部产品");
const [catalogLimit, setCatalogLimit] = useState(24);
const [selectedCatalogProduct, setSelectedCatalogProduct] = useState<CatalogProduct | null>(null);
const matches = useMemo(() => getMatches(requirements), [requirements]);
const topMatches = matches.slice(0, 3);
const top = topMatches[0];
const filteredCatalog = useMemo(() => {
const query = catalogQuery.trim().toLowerCase();
return CATALOG_PRODUCTS.filter((product) => {
const matchesCategory =
catalogCategory === "全部产品" || product.category === catalogCategory;
if (!matchesCategory) return false;
if (!query) return true;
const haystack = [
product.name,
product.code,
product.category,
...Object.keys(product.specs),
...Object.values(product.specs),
]
.join(" ")
.toLowerCase();
return haystack.includes(query);
});
}, [catalogCategory, catalogQuery]);
const visibleCatalog = filteredCatalog.slice(0, catalogLimit);
const needsEngineer =
top.score < 60 ||
requirements.annualVolume === "50000件以上" ||
requirements.application === "工业设备";
function update<K extends keyof Requirements>(key: K, value: Requirements[K]) {
setRequirements((current) => ({ ...current, [key]: value }));
}
function reset() {
setStage(1);
setRequirements(DEFAULT_REQUIREMENTS);
setShowSampleForm(false);
setSubmitted(false);
}
function submitLead(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const id = `GDL-${new Date().toISOString().slice(5, 10).replace("-", "")}-${String(
Date.now(),
).slice(-4)}`;
setLeadId(id);
setSubmitted(true);
}
return (
<main>
<header className="site-header">
<a className="brand" href="#top" aria-label="高徳乐 AI选型首页">
<span className="brand-mark">G</span>
<span>
<strong>GAUDELOT</strong>
<small> · </small>
</span>
</a>
<nav aria-label="主导航">
<a href="#selector">AI选型</a>
<a href="#products"></a>
<a href="#how"></a>
</nav>
<button className="header-cta" type="button" onClick={() => setStage(1)}>
<span></span>
</button>
</header>
<section className="hero" id="top">
<div className="hero-copy">
<div className="eyebrow"><span /> AI · </div>
<h1>
3<br />
<em></em>
</h1>
<p className="hero-lead">
AI从高徳乐标准产品中生成候选方案
</p>
<div className="hero-actions">
<button className="primary-action" type="button" onClick={() => setStage(1)}>
<span></span>
</button>
<a href="#products" className="text-action"></a>
</div>
<div className="trust-row">
<span><b>280</b> </span>
<span><b>0.09920</b> kgf·cm</span>
<span><b>48h</b> </span>
</div>
</div>
<div className="selector-shell" id="selector">
<div className="selector-topbar">
<div>
<span className="live-dot" /> AI
</div>
<span className="secure-note"></span>
</div>
{stage === 0 && (
<div className="welcome-panel">
<div className="assistant-avatar">AI</div>
<p className="assistant-label"></p>
<h2></h2>
<p>3</p>
<button className="panel-primary" type="button" onClick={() => setStage(1)}>
<span>3</span>
</button>
<div className="privacy-note"> </div>
</div>
)}
{stage > 0 && stage < 4 && (
<div className="question-panel">
<div className="progress-wrap" aria-label={`选型进度 ${stage}/3`}>
<div className="progress-labels">
<span></span>
<strong>{stage} / 3</strong>
</div>
<div className="progress-track"><i style={{ width: `${stage * 33.33}%` }} /></div>
</div>
{stage === 1 && (
<div className="question-step">
<p className="step-kicker"> · 使</p>
<h2></h2>
<p className="step-help"></p>
<div className="choice-grid">
{APPLICATIONS.map((item) => (
<button
type="button"
key={item.name}
className={`choice-card ${requirements.application === item.name ? "selected" : ""}`}
onClick={() => update("application", item.name)}
>
<b>{item.name}</b>
<small>{item.note}</small>
<span className="choice-check"></span>
</button>
))}
</div>
</div>
)}
{stage === 2 && (
<div className="question-step">
<p className="step-kicker"> · </p>
<h2></h2>
<p className="step-help"></p>
<div className="field-stack">
<label className="field-label" htmlFor="torque">
<strong>{requirements.torque} kgf·cm</strong>
</label>
<input
id="torque"
className="range-input"
type="range"
min="0.1"
max="24"
step="0.1"
value={requirements.torque}
onChange={(event) => update("torque", Number(event.target.value))}
/>
<div className="range-scale"><span>0.1</span><span>5</span><span>10</span><span>15</span><span>24</span></div>
</div>
<div className="two-fields">
<label className="select-field">
<span></span>
<select
value={requirements.voltage}
onChange={(event) => update("voltage", Number(event.target.value))}
>
<option value={3.7}>3.7V</option>
<option value={4.2}>4.2V</option>
<option value={5}>5.0V</option>
<option value={6}>6.0V</option>
<option value={7.4}>7.4V</option>
<option value={8.4}>8.4V</option>
</select>
</label>
<label className="select-field">
<span></span>
<select
value={requirements.weightLimit}
onChange={(event) => update("weightLimit", Number(event.target.value))}
>
<option value={2}>2g以内</option>
<option value={4}>4g以内</option>
<option value={10}>10g以内</option>
<option value={20}>20g以内</option>
<option value={40}>40g以内</option>
</select>
</label>
</div>
</div>
)}
{stage === 3 && (
<div className="question-step">
<p className="step-kicker"> · </p>
<h2></h2>
<p className="step-help"></p>
<div className="field-group">
<span className="field-title">齿</span>
<div className="pill-row">
{(["不限", "塑料齿", "半金属齿", "金属齿"] as const).map((gear) => (
<button
type="button"
key={gear}
className={requirements.gear === gear ? "active" : ""}
onClick={() => update("gear", gear)}
>{gear}</button>
))}
</div>
</div>
<div className="two-fields">
<label className="select-field">
<span></span>
<select
value={requirements.annualVolume}
onChange={(event) => update("annualVolume", event.target.value)}
>
<option>500</option>
<option>5005000</option>
<option>500050000</option>
<option>50000</option>
</select>
</label>
<label className="select-field">
<span></span>
<select
value={requirements.painPoint}
onChange={(event) => update("painPoint", event.target.value)}
>
<option></option>
<option></option>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</label>
</div>
<label className="text-field">
<span>使</span>
<input
value={requirements.currentModel}
onChange={(event) => update("currentModel", event.target.value)}
placeholder="例如SG90 / DS-E001 / 自定义型号"
/>
</label>
</div>
)}
<div className="panel-nav">
<button className="back-button" type="button" onClick={() => setStage(stage - 1)}> </button>
<button
className="next-button"
type="button"
disabled={stage === 1 && !requirements.application}
onClick={() => setStage(stage + 1)}
>
{stage === 3 ? "生成选型方案" : "继续"} <span></span>
</button>
</div>
</div>
)}
{stage === 4 && top && (
<div className="result-panel">
<div className="result-heading">
<div>
<p className="step-kicker">AI </p>
<h2> {topMatches.length} </h2>
</div>
<button type="button" className="restart-button" onClick={reset}></button>
</div>
<div className="best-match">
<div className="match-image-wrap">
<img src={top.product.image} alt={top.product.name} />
<span className="match-badge"></span>
</div>
<div className="match-content">
<div className="match-score"><span></span><strong>{top.confidence}%</strong></div>
<p className="product-code">{top.product.code}</p>
<h3>{top.product.name}</h3>
<p className="product-subtitle">{top.product.subtitle}</p>
<div className="metric-grid">
<Metric label="堵转扭矩" value={`${top.product.torque} kgf·cm`} />
<Metric label="空载速度" value={`${top.product.speed}s/60°`} />
<Metric label="工作电压" value={`${top.product.voltageMin}${top.product.voltageMax}V`} />
<Metric label="齿轮材质" value={top.product.gear} />
</div>
<ul className="reason-list">
{top.reasons.map((reason) => <li key={reason}> {reason}</li>)}
</ul>
<div className="price-line">
<span></span>
<strong>¥{top.product.price.toFixed(1)}</strong>
<small>/ · </small>
</div>
</div>
</div>
{needsEngineer && (
<div className="engineer-alert">
<span></span>
<p></p>
</div>
)}
<div className="alternative-list">
{topMatches.slice(1).map((match) => (
<div className="alternative-card" key={match.product.id}>
<img src={match.product.image} alt={match.product.name} />
<div>
<span>{match.confidence}% </span>
<h4>{match.product.name}</h4>
<p>{match.product.torque} kgf·cm · {match.product.gear} · ¥{match.product.price.toFixed(1)}</p>
</div>
</div>
))}
</div>
{!showSampleForm && (
<div className="result-actions">
<button className="panel-primary" type="button" onClick={() => setShowSampleForm(true)}>
<span></span>
</button>
<button className="outline-button" type="button" onClick={() => window.print()}> / </button>
</div>
)}
{showSampleForm && !submitted && (
<form className="sample-form" onSubmit={submitLead}>
<div>
<p className="step-kicker"></p>
<h3></h3>
<p>线CRM</p>
</div>
<div className="form-grid">
<label><span></span><input required name="company" placeholder="请输入公司名称" /></label>
<label><span></span><input required name="name" placeholder="姓名" /></label>
<label><span></span><input required name="phone" inputMode="tel" placeholder="用于样品确认" /></label>
<label><span></span><select name="quantity"><option>12</option><option>35</option><option>610</option></select></label>
</div>
<label className="full-field"><span></span><textarea name="notes" placeholder="安装尺寸、线长、角度、控制协议或测试时间要求" /></label>
<button className="panel-primary" type="submit"> <span></span></button>
<small className="demo-disclaimer"></small>
</form>
)}
{submitted && (
<div className="success-card">
<div className="success-icon"></div>
<p className="step-kicker">线</p>
<h3> {leadId}</h3>
<p>{top.product.name}线CRM并触发技术复核</p>
<button type="button" className="outline-button" onClick={reset}></button>
</div>
)}
</div>
)}
</div>
</section>
<section className="capability-strip" aria-label="高徳乐能力">
<div><strong>2</strong><span></span></div>
<div><strong>137</strong><span></span></div>
<div><strong>1/</strong><span>线</span></div>
<div><strong>IATF 16949</strong><span></span></div>
</section>
<section className="products-section" id="products">
<div className="section-heading">
<div>
<p className="eyebrow"><span /> · 2026.06</p>
<h2>280</h2>
</div>
<p>齿</p>
</div>
<div className="catalog-summary" aria-label="产品分类统计">
{CATALOG_CATEGORIES.slice(1).map((category) => (
<button
type="button"
key={category}
className={catalogCategory === category ? "active" : ""}
onClick={() => {
setCatalogCategory(category);
setCatalogLimit(24);
}}
>
<strong>{CATALOG_COUNTS[category]}</strong>
<span>{category}</span>
</button>
))}
</div>
<div className="catalog-toolbar">
<label className="catalog-search">
<span></span>
<input
value={catalogQuery}
onChange={(event) => {
setCatalogQuery(event.target.value);
setCatalogLimit(24);
}}
placeholder="搜索型号、编码、扭矩、协议或参数"
aria-label="搜索产品目录"
/>
{catalogQuery && (
<button type="button" onClick={() => setCatalogQuery("")} aria-label="清空搜索">×</button>
)}
</label>
<div className="category-pills" aria-label="产品类别筛选">
{CATALOG_CATEGORIES.map((category) => (
<button
type="button"
key={category}
className={catalogCategory === category ? "active" : ""}
onClick={() => {
setCatalogCategory(category);
setCatalogLimit(24);
}}
>{category}</button>
))}
</div>
<p className="catalog-result-count">
<strong>{filteredCatalog.length}</strong>
</p>
</div>
{visibleCatalog.length > 0 ? (
<div className="catalog-grid">
{visibleCatalog.map((product) => (
<article className="catalog-card" key={product.id}>
<button
type="button"
className="catalog-image-button"
onClick={() => setSelectedCatalogProduct(product)}
aria-label={`查看${product.name}详情`}
>
{product.image ? (
<img src={product.image} alt={product.name} loading="lazy" />
) : (
<span className="catalog-placeholder">GAUDELOT</span>
)}
<small>{product.category}</small>
</button>
<div className="catalog-card-body">
<p>{product.code}</p>
<h3>{product.name}</h3>
<dl>
{getFeaturedSpecs(product).map(([label, value]) => (
<div key={label}><dt>{label}</dt><dd>{value}</dd></div>
))}
</dl>
<button type="button" onClick={() => setSelectedCatalogProduct(product)}>
<span></span>
</button>
</div>
</article>
))}
</div>
) : (
<div className="catalog-empty">
<strong></strong>
<p></p>
<button type="button" onClick={() => { setCatalogQuery(""); setCatalogCategory("全部产品"); }}>
</button>
</div>
)}
{catalogLimit < filteredCatalog.length && (
<div className="load-more-wrap">
<button type="button" onClick={() => setCatalogLimit((current) => current + 24)}>
<span> {visibleCatalog.length} / {filteredCatalog.length}</span>
</button>
</div>
)}
</section>
{selectedCatalogProduct && (
<div
className="catalog-modal-backdrop"
role="presentation"
onMouseDown={() => setSelectedCatalogProduct(null)}
>
<section
className="catalog-modal"
role="dialog"
aria-modal="true"
aria-labelledby="catalog-modal-title"
onMouseDown={(event) => event.stopPropagation()}
>
<button
className="catalog-modal-close"
type="button"
onClick={() => setSelectedCatalogProduct(null)}
aria-label="关闭产品详情"
>×</button>
<div className="catalog-modal-product">
<div className="catalog-modal-image">
{selectedCatalogProduct.image ? (
<img src={selectedCatalogProduct.image} alt={selectedCatalogProduct.name} />
) : (
<span className="catalog-placeholder">GAUDELOT</span>
)}
</div>
<div>
<p className="step-kicker">{selectedCatalogProduct.category}</p>
<h2 id="catalog-modal-title">{selectedCatalogProduct.name}</h2>
<code>{selectedCatalogProduct.code}</code>
<p className="catalog-source-note"> {selectedCatalogProduct.page} </p>
</div>
</div>
<div className="catalog-spec-table">
{Object.entries(selectedCatalogProduct.specs).map(([label, value]) => (
<div key={label}><span>{label}</span><strong>{value}</strong></div>
))}
</div>
<div className="catalog-modal-actions">
<button
className="panel-primary"
type="button"
onClick={() => {
setSelectedCatalogProduct(null);
setStage(1);
window.scrollTo({ top: 0, behavior: "smooth" });
}}
>
AI继续确认需求 <span></span>
</button>
<p></p>
</div>
</section>
</div>
)}
<section className="how-section" id="how">
<div className="how-copy">
<p className="eyebrow light"><span /> </p>
<h2><br /></h2>
<p>AI负责需求采集线</p>
<button className="primary-action light-button" type="button" onClick={() => { setStage(1); window.scrollTo({ top: 0, behavior: "smooth" }); }}>
<span></span>
</button>
</div>
<div className="flow-list">
<div><b>01</b><span><strong></strong><small></small></span></div>
<div><b>02</b><span><strong>AI生成候选方案</strong><small></small></span></div>
<div><b>03</b><span><strong></strong><small>线</small></span></div>
<div><b>04</b><span><strong></strong><small></small></span></div>
</div>
</section>
<footer>
<div className="brand footer-brand">
<span className="brand-mark">G</span>
<span><strong>GAUDELOT</strong><small> · </small></span>
</div>
<p>AI选型结果仅供初步筛选</p>
<span> · V1</span>
</footer>
</main>
);
}