"use client"; import { FormEvent, useMemo, useState } from "react"; import { CATALOG_COUNTS, CATALOG_PRODUCTS, type CatalogProduct, } from "./catalog-data"; type Requirements = { application: string; category: string; torque: number; voltage: number; weightLimit: number; angle: string; control: string; gear: string; annualVolume: string; firstOrderQuantity: string; currentModel: string; otherRequirements: string; painPoint: string; }; 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 SERVO_CATEGORIES = ["标准舵机", "异形舵机", "全金属舵机", "大扭矩舵机", "积木舵机"]; const TORQUE_MIN = 0.09; const TORQUE_MAX = 920; const TORQUE_PRESETS = [0.1, 1, 5, 20, 100, 300, 920]; const APPLICATION_CATEGORY_MAP: Record = { AI毛绒: ["标准舵机", "异形舵机"], 智能玩具: ["标准舵机", "异形舵机", "积木舵机"], "教育/积木": ["积木舵机", "标准舵机"], 微型机器人: ["标准舵机", "异形舵机", "全金属舵机"], "云台/航模": ["标准舵机", "全金属舵机"], 工业设备: ["全金属舵机", "大扭矩舵机", "异形舵机"], }; function parseNumbers(value = "") { return (value.match(/\d+(?:\.\d+)?/g) ?? []).map(Number); } function inferWeight(product: CatalogProduct) { const listedWeight = parseNumbers(product.specs["重量"])[0]; if (Number.isFinite(listedWeight)) return listedWeight; if (product.category !== "标准舵机") return null; const namedWeight = product.name.match(/(\d+(?:\.\d+)?)\s*(?:g|克)/i); return namedWeight ? Number(namedWeight[1]) : null; } function classifyGear(value = "") { const hasPlastic = /塑胶|塑料/.test(value); const hasMetal = /金属|铜|粉末冶金|滚齿/.test(value); if (hasPlastic && hasMetal) return "混合齿"; if (hasMetal) return "全金属齿"; if (hasPlastic) return "塑胶齿"; return "未标注"; } function controlMatches(control: string, productControl: string) { if (control === "不限") return true; const value = productControl.toUpperCase(); if (control === "PWM/三线") return value.includes("PWM") || /3线|3PIN/.test(value); if (control === "串口/TTL") return value.includes("TTL") || value.includes("串口"); if (control === "二/四/五线") return /[245]线|[245]PIN/.test(value); return value.includes(control.toUpperCase()); } function angleMatches(angle: string, productAngle: string) { if (angle === "不限") return true; if (!productAngle) return false; const firstAngle = parseNumbers(productAngle)[0] ?? 0; if (angle === "135°") return firstAngle > 0 && firstAngle <= 135; if (angle === "180°") return firstAngle > 135 && firstAngle <= 180; if (angle === "270–330°") return firstAngle >= 270 && firstAngle < 360; return productAngle.includes("多圈") || firstAngle >= 360; } function formatTorque(value: number) { if (value < 1) return value.toFixed(2).replace(/0+$/, "").replace(/\.$/, ""); if (value < 100) return value.toFixed(1).replace(/\.0$/, ""); return Math.round(value).toString(); } function torqueToSlider(value: number) { return ((Math.log(value) - Math.log(TORQUE_MIN)) / (Math.log(TORQUE_MAX) - Math.log(TORQUE_MIN))) * 100; } function sliderToTorque(position: number) { const value = Math.exp( Math.log(TORQUE_MIN) + (position / 100) * (Math.log(TORQUE_MAX) - Math.log(TORQUE_MIN)), ); if (value < 1) return Number(value.toFixed(2)); if (value < 100) return Number(value.toFixed(1)); return Math.round(value); } const SELECTOR_PRODUCTS = CATALOG_PRODUCTS.filter((product) => SERVO_CATEGORIES.includes(product.category), ).map((product) => { const voltageValues = parseNumbers(product.specs["电压范围"]); const ratedVoltage = product.ratedVoltage ?? 0; const control = [ product.specs["信号类型"], product.specs["通讯方式"], product.specs["端子连接线"], product.specs["连接线类型"], ].filter(Boolean).join(" · "); return { ...product, torque: product.torqueValue ?? 0, voltageMin: voltageValues[0] ?? ratedVoltage, voltageMax: voltageValues.at(-1) ?? ratedVoltage, weight: inferWeight(product), gear: classifyGear(product.specs["齿轮材质"]), angle: product.specs["可操作角度"] ?? product.specs["操作角度范围"] ?? "", control, speed: product.specs["空载速度"] ?? product.specs["转动速度"] ?? "", size: product.specs["产品尺寸"] ?? product.specs["外形尺寸"] ?? "", }; }); const DEFAULT_REQUIREMENTS: Requirements = { application: "", category: "全部舵机", torque: 2, voltage: 5, weightLimit: 0, angle: "不限", control: "不限", gear: "不限", annualVolume: "5000–50000件", firstOrderQuantity: "", currentModel: "", otherRequirements: "", painPoint: "交期", }; function getMatches(requirements: Requirements) { return SELECTOR_PRODUCTS.map((product) => { let score = 20; const reasons: string[] = []; const categoryOk = requirements.category === "全部舵机" || requirements.category === product.category; const torqueOk = product.torque >= requirements.torque; const voltageOk = requirements.voltage >= product.voltageMin && requirements.voltage <= product.voltageMax; const weightOk = requirements.weightLimit === 0 || product.weight === null || product.weight <= requirements.weightLimit; const gearOk = requirements.gear === "不限" || requirements.gear === product.gear; const angleOk = angleMatches(requirements.angle, product.angle); const controlOk = controlMatches(requirements.control, product.control); const coreCompatible = categoryOk && torqueOk && voltageOk && weightOk && gearOk && angleOk && controlOk; if (categoryOk) score += requirements.category === "全部舵机" ? 6 : 16; else score -= 120; if (APPLICATION_CATEGORY_MAP[requirements.application]?.includes(product.category)) { score += 12; reasons.push(`适合${requirements.application}场景`); } if (torqueOk) { const ratio = product.torque / requirements.torque; score += Math.max(5, 28 - Math.log10(Math.max(1, ratio)) * 9); const margin = Math.round((product.torque / requirements.torque - 1) * 100); reasons.push(`扭矩满足,约有${Math.max(0, margin)}%余量`); } else { score -= 120; } if (voltageOk) { score += 22; reasons.push(`${requirements.voltage}V供电兼容`); } else { score -= 90; } if (requirements.weightLimit > 0) { if (product.weight === null) score -= 6; else if (weightOk) { score += 10; reasons.push(`重量${product.weight}g,符合上限`); } else score -= 45; } if (requirements.gear !== "不限") score += gearOk ? 10 : -40; if (requirements.angle !== "不限") score += angleOk ? 10 : -40; if (requirements.control !== "不限") score += controlOk ? 12 : -50; if (requirements.painPoint === "成本" && product.gear === "塑料齿") score += 8; if (requirements.painPoint === "耐久" && product.gear === "全金属齿") score += 8; if (requirements.painPoint === "体积" && product.weight !== null && product.weight <= 9) score += 8; return { product, score, coreCompatible, confidence: Math.max(48, Math.min(97, Math.round(score))), reasons: reasons.slice(0, 3), }; }).sort((a, b) => Number(b.coreCompatible) - Number(a.coreCompatible) || b.score - a.score); } function Metric({ label, value }: { label: string; value: string }) { return (
{label} {value}
); } 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(null); const matches = useMemo(() => getMatches(requirements), [requirements]); const topMatches = matches.slice(0, 3); const top = topMatches[0]; const compatibleCount = matches.filter((match) => match.coreCompatible).length; 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?.coreCompatible || requirements.annualVolume === "50000件以上" || Number(requirements.firstOrderQuantity) >= 1000 || requirements.application === "工业设备"; function update(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) { event.preventDefault(); const id = `GDL-${new Date().toISOString().slice(5, 10).replace("-", "")}-${String( Date.now(), ).slice(-4)}`; setLeadId(id); setSubmitted(true); } return (
G GAUDELOT 高徳乐 · 精密传动
AI 智能选型 · 工程师复核

3分钟,找到
适合你的舵机

告诉我们应用、扭矩与结构限制。AI从高徳乐完整舵机目录中生成候选方案, 同步准备样品与替代选型摘要。

查看能力范围
280 个产品型号 0.09–920 kgf·cm 48h 方案响应
AI 选型助手
仅作初选建议
{stage === 0 && (
AI

高徳乐选型助手

你好,我来帮你缩小选型范围。

回答3组问题,即可获得推荐型号、匹配理由和样品申请入口。

✓ 无需注册即可查看选型结果
)} {stage > 0 && stage < 4 && (
需求识别 {stage} / 3
{stage === 1 && (

第一步 · 使用场景

舵机将用在什么产品上?

选择最接近的一项,后续可以补充具体结构。

{APPLICATIONS.map((item) => ( ))}
)} {stage === 2 && (

第二步 · 核心参数

输入舵机的关键技术要求

已覆盖243款舵机:0.09–920 kgf·cm、0.8–30V;不确定的项目可选“不限”。

update( "torque", Math.min(TORQUE_MAX, Math.max(TORQUE_MIN, Number(event.target.value) || TORQUE_MIN)), )} /> kgf·cm
update("torque", sliderToTorque(Number(event.target.value)))} />
0.09110100920
{TORQUE_PRESETS.map((torque) => ( ))}
)} {stage === 3 && (

第三步 · 项目信息

最后,告诉我采购侧的偏好。

这些信息用于调整候选型号的优先级,并由工程师在送样前复核。