Expand selector to full servo catalog
This commit is contained in:
@@ -137,17 +137,25 @@ a { color: inherit; text-decoration: none; }
|
|||||||
.back-button, .restart-button { padding: 11px 2px; border: 0; color: #798599; background: transparent; cursor: pointer; font-size: 12px; }
|
.back-button, .restart-button { padding: 11px 2px; border: 0; color: #798599; background: transparent; cursor: pointer; font-size: 12px; }
|
||||||
.next-button { min-width: 150px; padding: 13px 20px; }
|
.next-button { min-width: 150px; padding: 13px 20px; }
|
||||||
.next-button:disabled { cursor: not-allowed; opacity: .35; transform: none; box-shadow: none; }
|
.next-button:disabled { cursor: not-allowed; opacity: .35; transform: none; box-shadow: none; }
|
||||||
.field-stack { padding: 18px 18px 15px; border-radius: 12px; background: #f6f8fc; }
|
.field-stack { margin-top: 13px; padding: 15px 16px 13px; border-radius: 12px; background: #f6f8fc; }
|
||||||
.field-label { display: flex; justify-content: space-between; color: #657187; font-size: 12px; }
|
.field-label { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: #657187; font-size: 12px; }
|
||||||
.field-label strong { color: var(--blue); font-size: 15px; }
|
.field-label strong { color: var(--blue); font-size: 15px; }
|
||||||
.range-input { width: 100%; margin: 24px 0 6px; accent-color: var(--blue); }
|
.torque-entry { display: flex; align-items: center; gap: 6px; }
|
||||||
|
.torque-entry input { width: 86px; height: 34px; padding: 0 9px; text-align: right; font-weight: 750; }
|
||||||
|
.torque-entry strong { font-size: 10px; white-space: nowrap; }
|
||||||
|
.range-input { width: 100%; margin: 16px 0 5px; accent-color: var(--blue); }
|
||||||
.range-scale { display: flex; justify-content: space-between; color: #9aa3b1; font-size: 9px; }
|
.range-scale { display: flex; justify-content: space-between; color: #9aa3b1; font-size: 9px; }
|
||||||
|
.torque-presets { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin-top: 10px; }
|
||||||
|
.torque-presets button { min-width: 0; padding: 6px 2px; border: 1px solid #d8e0eb; border-radius: 7px; color: #657187; background: white; cursor: pointer; font-size: 9px; }
|
||||||
|
.torque-presets button.active { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); font-weight: 750; }
|
||||||
.two-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 15px; }
|
.two-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 15px; }
|
||||||
|
.compact-fields { margin-top: 0; }
|
||||||
|
.parameter-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; margin-top: 13px; }
|
||||||
.select-field, .text-field { display: grid; gap: 7px; }
|
.select-field, .text-field { display: grid; gap: 7px; }
|
||||||
.select-field span, .text-field span, .field-title, .sample-form label span { color: #68758a; font-size: 11px; font-weight: 650; }
|
.select-field span, .text-field span, .field-title, .sample-form label span { color: #68758a; font-size: 11px; font-weight: 650; }
|
||||||
select, input, textarea { outline: none; border: 1px solid #dbe2ec; border-radius: 9px; color: var(--ink); background: white; }
|
select, input, textarea { outline: none; border: 1px solid #dbe2ec; border-radius: 9px; color: var(--ink); background: white; }
|
||||||
select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(24,94,232,.1); }
|
select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(24,94,232,.1); }
|
||||||
.select-field select { height: 46px; padding: 0 12px; }
|
.select-field select, .parameter-input { width: 100%; height: 43px; padding: 0 12px; }
|
||||||
.text-field { margin-top: 13px; }
|
.text-field { margin-top: 13px; }
|
||||||
.text-field input { height: 44px; padding: 0 13px; }
|
.text-field input { height: 44px; padding: 0 13px; }
|
||||||
.field-group { margin: 17px 0 13px; }
|
.field-group { margin: 17px 0 13px; }
|
||||||
@@ -159,7 +167,7 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
|||||||
.result-heading { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }
|
.result-heading { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }
|
||||||
.best-match { display: grid; grid-template-columns: 180px 1fr; border: 1px solid #cfdaf0; border-radius: 15px; overflow: hidden; box-shadow: 0 8px 24px rgba(38,70,125,.08); }
|
.best-match { display: grid; grid-template-columns: 180px 1fr; border: 1px solid #cfdaf0; border-radius: 15px; overflow: hidden; box-shadow: 0 8px 24px rgba(38,70,125,.08); }
|
||||||
.match-image-wrap { position: relative; min-height: 270px; background: #eff4fb; }
|
.match-image-wrap { position: relative; min-height: 270px; background: #eff4fb; }
|
||||||
.match-image-wrap img { width: 100%; height: 100%; object-fit: cover; }
|
.match-image-wrap img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
|
||||||
.match-badge { position: absolute; left: 12px; top: 12px; padding: 6px 9px; border-radius: 7px; color: white; background: var(--orange); font-size: 9px; font-weight: 800; }
|
.match-badge { position: absolute; left: 12px; top: 12px; padding: 6px 9px; border-radius: 7px; color: white; background: var(--orange); font-size: 9px; font-weight: 800; }
|
||||||
.match-content { position: relative; padding: 19px 19px 16px; }
|
.match-content { position: relative; padding: 19px 19px 16px; }
|
||||||
.match-score { position: absolute; right: 16px; top: 15px; display: grid; text-align: right; color: #7c889a; font-size: 9px; }
|
.match-score { position: absolute; right: 16px; top: 15px; display: grid; text-align: right; color: #7c889a; font-size: 9px; }
|
||||||
@@ -176,6 +184,10 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
|||||||
.price-line span { color: #7c8798; font-size: 9px; }
|
.price-line span { color: #7c8798; font-size: 9px; }
|
||||||
.price-line strong { color: #e55726; font-size: 20px; }
|
.price-line strong { color: #e55726; font-size: 20px; }
|
||||||
.price-line small { color: #9aa4b3; font-size: 8px; }
|
.price-line small { color: #9aa4b3; font-size: 8px; }
|
||||||
|
.source-line { display: flex; align-items: center; gap: 7px; padding-top: 9px; border-top: 1px solid #edf0f4; }
|
||||||
|
.source-line span { color: #7c8798; font-size: 9px; }
|
||||||
|
.source-line strong { color: var(--blue); font-size: 10px; }
|
||||||
|
.source-line small { margin-left: auto; color: #9aa4b3; font-size: 8px; }
|
||||||
.engineer-alert { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid #f0d29e; border-radius: 9px; background: #fff9ef; }
|
.engineer-alert { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid #f0d29e; border-radius: 9px; background: #fff9ef; }
|
||||||
.engineer-alert span { flex: 0 0 auto; padding: 4px 7px; border-radius: 5px; color: #9b5a0a; background: #ffe7bc; font-size: 8px; font-weight: 800; }
|
.engineer-alert span { flex: 0 0 auto; padding: 4px 7px; border-radius: 5px; color: #9b5a0a; background: #ffe7bc; font-size: 8px; font-weight: 800; }
|
||||||
.engineer-alert p { margin: 0; color: #815e2f; font-size: 9px; }
|
.engineer-alert p { margin: 0; color: #815e2f; font-size: 9px; }
|
||||||
@@ -439,7 +451,8 @@ footer > span { color: #9aa4b2; font-size: 10px; }
|
|||||||
.selector-shell { border-radius: 16px; }
|
.selector-shell { border-radius: 16px; }
|
||||||
.question-panel, .result-panel { padding: 20px 16px; }
|
.question-panel, .result-panel { padding: 20px 16px; }
|
||||||
.welcome-panel { padding: 55px 20px 35px; }
|
.welcome-panel { padding: 55px 20px 35px; }
|
||||||
.choice-grid, .two-fields, .form-grid, .alternative-list { grid-template-columns: 1fr; }
|
.choice-grid, .two-fields, .parameter-grid, .form-grid, .alternative-list { grid-template-columns: 1fr; }
|
||||||
|
.torque-presets { grid-template-columns: repeat(4, 1fr); }
|
||||||
.best-match { grid-template-columns: 1fr; }
|
.best-match { grid-template-columns: 1fr; }
|
||||||
.match-image-wrap { height: 240px; }
|
.match-image-wrap { height: 240px; }
|
||||||
.capability-strip { grid-template-columns: 1fr 1fr; gap: 24px 0; padding: 28px 16px; }
|
.capability-strip { grid-template-columns: 1fr 1fr; gap: 24px 0; padding: 28px 16px; }
|
||||||
|
|||||||
593
app/page.tsx
593
app/page.tsx
@@ -7,247 +7,20 @@ import {
|
|||||||
type CatalogProduct,
|
type CatalogProduct,
|
||||||
} from "./catalog-data";
|
} 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 = {
|
type Requirements = {
|
||||||
application: string;
|
application: string;
|
||||||
|
category: string;
|
||||||
torque: number;
|
torque: number;
|
||||||
voltage: number;
|
voltage: number;
|
||||||
weightLimit: number;
|
weightLimit: number;
|
||||||
gear: "不限" | Gear;
|
angle: string;
|
||||||
|
control: string;
|
||||||
|
gear: string;
|
||||||
annualVolume: string;
|
annualVolume: string;
|
||||||
currentModel: string;
|
currentModel: string;
|
||||||
painPoint: 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 = [
|
const APPLICATIONS = [
|
||||||
{ name: "AI毛绒", note: "头部、耳朵、手臂等轻量动作" },
|
{ name: "AI毛绒", note: "头部、耳朵、手臂等轻量动作" },
|
||||||
{ name: "智能玩具", note: "互动机构、表情和行走结构" },
|
{ name: "智能玩具", note: "互动机构、表情和行走结构" },
|
||||||
@@ -287,11 +60,112 @@ function getFeaturedSpecs(product: CatalogProduct) {
|
|||||||
.map((label) => [label, product.specs[label]] as const);
|
.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<string, string[]> = {
|
||||||
|
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 = {
|
const DEFAULT_REQUIREMENTS: Requirements = {
|
||||||
application: "",
|
application: "",
|
||||||
|
category: "全部舵机",
|
||||||
torque: 2,
|
torque: 2,
|
||||||
voltage: 5,
|
voltage: 5,
|
||||||
weightLimit: 40,
|
weightLimit: 0,
|
||||||
|
angle: "不限",
|
||||||
|
control: "不限",
|
||||||
gear: "不限",
|
gear: "不限",
|
||||||
annualVolume: "5000–50000件",
|
annualVolume: "5000–50000件",
|
||||||
currentModel: "",
|
currentModel: "",
|
||||||
@@ -299,58 +173,69 @@ const DEFAULT_REQUIREMENTS: Requirements = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getMatches(requirements: Requirements) {
|
function getMatches(requirements: Requirements) {
|
||||||
return PRODUCTS.map((product) => {
|
return SELECTOR_PRODUCTS.map((product) => {
|
||||||
let score = 34;
|
let score = 20;
|
||||||
const reasons: string[] = [];
|
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 (product.applications.includes(requirements.application)) {
|
if (categoryOk) score += requirements.category === "全部舵机" ? 6 : 16;
|
||||||
score += 18;
|
else score -= 120;
|
||||||
|
|
||||||
|
if (APPLICATION_CATEGORY_MAP[requirements.application]?.includes(product.category)) {
|
||||||
|
score += 12;
|
||||||
reasons.push(`适合${requirements.application}场景`);
|
reasons.push(`适合${requirements.application}场景`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (product.torque >= requirements.torque) {
|
if (torqueOk) {
|
||||||
score += 25;
|
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);
|
const margin = Math.round((product.torque / requirements.torque - 1) * 100);
|
||||||
reasons.push(`扭矩满足,约有${Math.max(0, margin)}%余量`);
|
reasons.push(`扭矩满足,约有${Math.max(0, margin)}%余量`);
|
||||||
} else {
|
} else {
|
||||||
score -= 70;
|
score -= 120;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (voltageOk) {
|
||||||
requirements.voltage >= product.voltageMin &&
|
score += 22;
|
||||||
requirements.voltage <= product.voltageMax
|
|
||||||
) {
|
|
||||||
score += 14;
|
|
||||||
reasons.push(`${requirements.voltage}V供电兼容`);
|
reasons.push(`${requirements.voltage}V供电兼容`);
|
||||||
} else {
|
} else {
|
||||||
score -= 35;
|
score -= 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (product.weight <= requirements.weightLimit) {
|
if (requirements.weightLimit > 0) {
|
||||||
|
if (product.weight === null) score -= 6;
|
||||||
|
else if (weightOk) {
|
||||||
score += 10;
|
score += 10;
|
||||||
reasons.push(`重量不超过${requirements.weightLimit}g`);
|
reasons.push(`重量${product.weight}g,符合上限`);
|
||||||
} else {
|
} else score -= 45;
|
||||||
score -= 45;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requirements.gear === "不限" || requirements.gear === product.gear) {
|
if (requirements.gear !== "不限") score += gearOk ? 10 : -40;
|
||||||
score += requirements.gear === "不限" ? 4 : 12;
|
if (requirements.angle !== "不限") score += angleOk ? 10 : -40;
|
||||||
if (requirements.gear !== "不限") reasons.push(`${product.gear}符合偏好`);
|
if (requirements.control !== "不限") score += controlOk ? 12 : -50;
|
||||||
} 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.gear === "金属齿") score += 8;
|
if (requirements.painPoint === "耐久" && product.gear === "全金属齿") score += 8;
|
||||||
if (requirements.painPoint === "体积" && product.weight <= 9) score += 8;
|
if (requirements.painPoint === "体积" && product.weight !== null && product.weight <= 9) score += 8;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
product,
|
product,
|
||||||
score,
|
score,
|
||||||
confidence: Math.max(52, Math.min(96, score)),
|
coreCompatible,
|
||||||
|
confidence: Math.max(48, Math.min(97, Math.round(score))),
|
||||||
reasons: reasons.slice(0, 3),
|
reasons: reasons.slice(0, 3),
|
||||||
};
|
};
|
||||||
}).sort((a, b) => b.score - a.score);
|
}).sort((a, b) => Number(b.coreCompatible) - Number(a.coreCompatible) || b.score - a.score);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Metric({ label, value }: { label: string; value: string }) {
|
function Metric({ label, value }: { label: string; value: string }) {
|
||||||
@@ -376,6 +261,7 @@ export default function Home() {
|
|||||||
const matches = useMemo(() => getMatches(requirements), [requirements]);
|
const matches = useMemo(() => getMatches(requirements), [requirements]);
|
||||||
const topMatches = matches.slice(0, 3);
|
const topMatches = matches.slice(0, 3);
|
||||||
const top = topMatches[0];
|
const top = topMatches[0];
|
||||||
|
const compatibleCount = matches.filter((match) => match.coreCompatible).length;
|
||||||
const filteredCatalog = useMemo(() => {
|
const filteredCatalog = useMemo(() => {
|
||||||
const query = catalogQuery.trim().toLowerCase();
|
const query = catalogQuery.trim().toLowerCase();
|
||||||
return CATALOG_PRODUCTS.filter((product) => {
|
return CATALOG_PRODUCTS.filter((product) => {
|
||||||
@@ -397,7 +283,7 @@ export default function Home() {
|
|||||||
}, [catalogCategory, catalogQuery]);
|
}, [catalogCategory, catalogQuery]);
|
||||||
const visibleCatalog = filteredCatalog.slice(0, catalogLimit);
|
const visibleCatalog = filteredCatalog.slice(0, catalogLimit);
|
||||||
const needsEngineer =
|
const needsEngineer =
|
||||||
top.score < 60 ||
|
!top?.coreCompatible ||
|
||||||
requirements.annualVolume === "50000件以上" ||
|
requirements.annualVolume === "50000件以上" ||
|
||||||
requirements.application === "工业设备";
|
requirements.application === "工业设备";
|
||||||
|
|
||||||
@@ -449,7 +335,7 @@ export default function Home() {
|
|||||||
<em>适合你的舵机</em>
|
<em>适合你的舵机</em>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="hero-lead">
|
<p className="hero-lead">
|
||||||
告诉我们应用、扭矩与结构限制。AI从高徳乐标准产品中生成候选方案,
|
告诉我们应用、扭矩与结构限制。AI从高徳乐完整舵机目录中生成候选方案,
|
||||||
同步准备样品与替代选型摘要。
|
同步准备样品与替代选型摘要。
|
||||||
</p>
|
</p>
|
||||||
<div className="hero-actions">
|
<div className="hero-actions">
|
||||||
@@ -521,50 +407,134 @@ export default function Home() {
|
|||||||
{stage === 2 && (
|
{stage === 2 && (
|
||||||
<div className="question-step">
|
<div className="question-step">
|
||||||
<p className="step-kicker">第二步 · 核心参数</p>
|
<p className="step-kicker">第二步 · 核心参数</p>
|
||||||
<h2>需要多大的输出能力?</h2>
|
<h2>输入舵机的关键技术要求</h2>
|
||||||
<p className="step-help">不确定时可填写估算值,工程师会在送样前复核。</p>
|
<p className="step-help">已覆盖243款舵机:0.09–920 kgf·cm、0.8–30V;不确定的项目可选“不限”。</p>
|
||||||
<div className="field-stack">
|
<div className="two-fields compact-fields">
|
||||||
<label className="field-label" htmlFor="torque">
|
<label className="select-field">
|
||||||
最低堵转扭矩 <strong>{requirements.torque} kgf·cm</strong>
|
<span>目标产品系列</span>
|
||||||
|
<select
|
||||||
|
value={requirements.category}
|
||||||
|
onChange={(event) => update("category", event.target.value)}
|
||||||
|
>
|
||||||
|
<option value="全部舵机">全部舵机(243款)</option>
|
||||||
|
{SERVO_CATEGORIES.map((category) => (
|
||||||
|
<option key={category} value={category}>
|
||||||
|
{category}({CATALOG_COUNTS[category]}款)
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label className="select-field">
|
||||||
|
<span>系统工作电压(0.8–30V)</span>
|
||||||
|
<input
|
||||||
|
className="parameter-input"
|
||||||
|
type="number"
|
||||||
|
min="0.8"
|
||||||
|
max="30"
|
||||||
|
step="0.1"
|
||||||
|
list="voltage-values"
|
||||||
|
value={requirements.voltage}
|
||||||
|
onChange={(event) => update("voltage", Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
<datalist id="voltage-values">
|
||||||
|
{[1.2, 2.7, 3.7, 4.2, 5, 6, 7.4, 8.4, 9, 12, 16.8, 24, 28, 30].map((voltage) => (
|
||||||
|
<option key={voltage} value={voltage} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="field-stack">
|
||||||
|
<div className="field-label">
|
||||||
|
<label htmlFor="torque">最低堵转扭矩</label>
|
||||||
|
<span className="torque-entry">
|
||||||
|
<input
|
||||||
|
aria-label="最低堵转扭矩精确值"
|
||||||
|
type="number"
|
||||||
|
min={TORQUE_MIN}
|
||||||
|
max={TORQUE_MAX}
|
||||||
|
step="0.01"
|
||||||
|
value={requirements.torque}
|
||||||
|
onChange={(event) => update(
|
||||||
|
"torque",
|
||||||
|
Math.min(TORQUE_MAX, Math.max(TORQUE_MIN, Number(event.target.value) || TORQUE_MIN)),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<strong>kgf·cm</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
id="torque"
|
id="torque"
|
||||||
className="range-input"
|
className="range-input"
|
||||||
type="range"
|
type="range"
|
||||||
min="0.1"
|
min="0"
|
||||||
max="24"
|
max="100"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
value={requirements.torque}
|
value={torqueToSlider(requirements.torque)}
|
||||||
onChange={(event) => update("torque", Number(event.target.value))}
|
onChange={(event) => update("torque", sliderToTorque(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 className="range-scale"><span>0.09</span><span>1</span><span>10</span><span>100</span><span>920</span></div>
|
||||||
|
<div className="torque-presets" aria-label="常用扭矩快捷值">
|
||||||
|
{TORQUE_PRESETS.map((torque) => (
|
||||||
|
<button
|
||||||
|
key={torque}
|
||||||
|
type="button"
|
||||||
|
className={requirements.torque === torque ? "active" : ""}
|
||||||
|
onClick={() => update("torque", torque)}
|
||||||
|
>{formatTorque(torque)}</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="two-fields">
|
</div>
|
||||||
<label className="select-field">
|
<div className="parameter-grid">
|
||||||
<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">
|
<label className="select-field">
|
||||||
<span>单只重量上限</span>
|
<span>单只重量上限</span>
|
||||||
<select
|
<select
|
||||||
value={requirements.weightLimit}
|
value={requirements.weightLimit}
|
||||||
onChange={(event) => update("weightLimit", Number(event.target.value))}
|
onChange={(event) => update("weightLimit", Number(event.target.value))}
|
||||||
>
|
>
|
||||||
<option value={2}>2g以内</option>
|
<option value={0}>不限</option>
|
||||||
<option value={4}>4g以内</option>
|
{[2, 5, 10, 20, 40, 100, 250, 500, 1000, 2000].map((weight) => (
|
||||||
<option value={10}>10g以内</option>
|
<option key={weight} value={weight}>{weight}g以内</option>
|
||||||
<option value={20}>20g以内</option>
|
))}
|
||||||
<option value={40}>40g以内</option>
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="select-field">
|
||||||
|
<span>操作角度</span>
|
||||||
|
<select
|
||||||
|
value={requirements.angle}
|
||||||
|
onChange={(event) => update("angle", event.target.value)}
|
||||||
|
>
|
||||||
|
<option>不限</option>
|
||||||
|
<option>135°</option>
|
||||||
|
<option>180°</option>
|
||||||
|
<option>270–330°</option>
|
||||||
|
<option>360°/多圈</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="select-field">
|
||||||
|
<span>控制 / 通讯方式</span>
|
||||||
|
<select
|
||||||
|
value={requirements.control}
|
||||||
|
onChange={(event) => update("control", event.target.value)}
|
||||||
|
>
|
||||||
|
<option>不限</option>
|
||||||
|
<option>PWM/三线</option>
|
||||||
|
<option>串口/TTL</option>
|
||||||
|
<option>RS485</option>
|
||||||
|
<option>CAN</option>
|
||||||
|
<option>SBUS</option>
|
||||||
|
<option>二/四/五线</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="select-field">
|
||||||
|
<span>齿轮材质</span>
|
||||||
|
<select
|
||||||
|
value={requirements.gear}
|
||||||
|
onChange={(event) => update("gear", event.target.value)}
|
||||||
|
>
|
||||||
|
<option>不限</option>
|
||||||
|
<option>塑胶齿</option>
|
||||||
|
<option>混合齿</option>
|
||||||
|
<option>全金属齿</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -575,20 +545,7 @@ export default function Home() {
|
|||||||
<div className="question-step">
|
<div className="question-step">
|
||||||
<p className="step-kicker">第三步 · 项目信息</p>
|
<p className="step-kicker">第三步 · 项目信息</p>
|
||||||
<h2>最后,告诉我采购侧的偏好。</h2>
|
<h2>最后,告诉我采购侧的偏好。</h2>
|
||||||
<p className="step-help">这些信息用于计算推荐优先级,不会改变公开参考价。</p>
|
<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">
|
<div className="two-fields">
|
||||||
<label className="select-field">
|
<label className="select-field">
|
||||||
<span>预计年度用量</span>
|
<span>预计年度用量</span>
|
||||||
@@ -647,7 +604,7 @@ export default function Home() {
|
|||||||
<div className="result-heading">
|
<div className="result-heading">
|
||||||
<div>
|
<div>
|
||||||
<p className="step-kicker">AI 初选结果</p>
|
<p className="step-kicker">AI 初选结果</p>
|
||||||
<h2>找到 {topMatches.length} 个候选方案</h2>
|
<h2>{compatibleCount > 0 ? `找到 ${compatibleCount} 个匹配型号` : "暂无完全匹配型号"}</h2>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" className="restart-button" onClick={reset}>重新选型</button>
|
<button type="button" className="restart-button" onClick={reset}>重新选型</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -655,26 +612,28 @@ export default function Home() {
|
|||||||
<div className="best-match">
|
<div className="best-match">
|
||||||
<div className="match-image-wrap">
|
<div className="match-image-wrap">
|
||||||
<img src={top.product.image} alt={top.product.name} />
|
<img src={top.product.image} alt={top.product.name} />
|
||||||
<span className="match-badge">首选推荐</span>
|
<span className="match-badge">{top.coreCompatible ? "首选推荐" : "待复核候选"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="match-content">
|
<div className="match-content">
|
||||||
<div className="match-score"><span>匹配度</span><strong>{top.confidence}%</strong></div>
|
<div className="match-score"><span>匹配度</span><strong>{top.confidence}%</strong></div>
|
||||||
<p className="product-code">{top.product.code}</p>
|
<p className="product-code">{top.product.code}</p>
|
||||||
<h3>{top.product.name}</h3>
|
<h3>{top.product.name}</h3>
|
||||||
<p className="product-subtitle">{top.product.subtitle}</p>
|
<p className="product-subtitle">{top.product.category} · {top.product.size || "尺寸待复核"}</p>
|
||||||
<div className="metric-grid">
|
<div className="metric-grid">
|
||||||
<Metric label="堵转扭矩" value={`${top.product.torque} kgf·cm`} />
|
<Metric label="堵转扭矩" value={`${formatTorque(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.voltageMin}–${top.product.voltageMax}V`} />
|
||||||
|
<Metric label="空载速度" value={top.product.speed || "目录未标注"} />
|
||||||
|
<Metric label="操作角度" value={top.product.angle || "目录未标注"} />
|
||||||
|
<Metric label="控制 / 通讯" value={top.product.control || "目录未标注"} />
|
||||||
<Metric label="齿轮材质" value={top.product.gear} />
|
<Metric label="齿轮材质" value={top.product.gear} />
|
||||||
</div>
|
</div>
|
||||||
<ul className="reason-list">
|
<ul className="reason-list">
|
||||||
{top.reasons.map((reason) => <li key={reason}>✓ {reason}</li>)}
|
{top.reasons.map((reason) => <li key={reason}>✓ {reason}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="price-line">
|
<div className="source-line">
|
||||||
<span>商城参考价</span>
|
<span>官方目录型号</span>
|
||||||
<strong>¥{top.product.price.toFixed(1)}</strong>
|
<strong>{top.product.category}</strong>
|
||||||
<small>/ 只 · 批量价需确认</small>
|
<small>价格与交期按用量确认</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -682,7 +641,9 @@ export default function Home() {
|
|||||||
{needsEngineer && (
|
{needsEngineer && (
|
||||||
<div className="engineer-alert">
|
<div className="engineer-alert">
|
||||||
<span>工程师复核</span>
|
<span>工程师复核</span>
|
||||||
<p>你的项目包含高用量、工业场景或边界参数,系统已标记为优先人工复核。</p>
|
<p>{compatibleCount === 0
|
||||||
|
? "当前条件没有完全匹配的目录型号,建议调整电压、扭矩余量或发起定制评估。"
|
||||||
|
: "你的项目包含高用量、工业场景或边界参数,系统已标记为优先人工复核。"}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -693,7 +654,7 @@ export default function Home() {
|
|||||||
<div>
|
<div>
|
||||||
<span>{match.confidence}% 匹配</span>
|
<span>{match.confidence}% 匹配</span>
|
||||||
<h4>{match.product.name}</h4>
|
<h4>{match.product.name}</h4>
|
||||||
<p>{match.product.torque} kgf·cm · {match.product.gear} · ¥{match.product.price.toFixed(1)}</p>
|
<p>{formatTorque(match.product.torque)} kgf·cm · {match.product.category} · {match.product.gear}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user