From 49ca411592b2a1cc004061186ea093bc7752a728 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 29 Jul 2026 02:07:16 +0800 Subject: [PATCH] Update App.jsx via upload script - 2026-07-29 02:06:53 --- App.jsx | 98 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/App.jsx b/App.jsx index bfe9559..3cb4cae 100644 --- a/App.jsx +++ b/App.jsx @@ -291,8 +291,8 @@ const TRANSLATION_DICTIONARY = { '产品大类': { en: 'Category', fr: 'Catégorie', ru: 'Категория', es: 'Categoría', ar: 'الفئة' }, '细分品类': { en: 'Type', fr: 'Type', ru: 'Тип', es: 'Tipo', ar: 'النوع' }, '机型': { en: 'Machine Type', fr: "Type d'appareil", ru: 'Тип модели', es: 'Tipo de equipo', ar: 'طراز الجهاز' }, - '标机': { en: 'Standard', fr: 'Standard', ru: 'Стандартный', es: 'Estándar', ar: 'قياسي' }, - '长机': { en: 'Extended', fr: 'Prolongé', ru: 'Увеличенный', es: 'Extendido', ar: 'ممتد' }, + '标准机型': { en: 'Standard Model', fr: 'Modèle standard', ru: 'Стандартная модель', es: 'Modelo estándar', ar: 'الطراز القياسي' }, + '长延时机型': { en: 'Long Backup Time Model', fr: 'Modèle longue autonomie', ru: 'Модель увеличенной автономности', es: 'Modelo de larga autonomía', ar: 'طراز الدعم الطويل' }, '输出功率': { en: 'Output Power', fr: 'Puissance de sortie', ru: 'Выходная мощность', es: 'Potencia de salida', ar: 'طاقة الخرج' }, '功率 / 容量': { en: 'Power / Capacity', fr: 'Puissance / Capacité', ru: 'Мощность / Ёмкость', es: 'Potencia / Capacidad', ar: 'الطاقة / السعة' }, '输入电压': { en: 'Input Voltage', fr: "Tension d'entrée", ru: 'Входное напряжение', es: 'Voltaje de entrada', ar: 'جهد الدخل' }, @@ -315,6 +315,7 @@ const TRANSLATION_DICTIONARY = { '控制管理': { en: 'Interface', fr: 'Interface', ru: 'Интерфейс', es: 'Interfaz', ar: 'الواجهة' }, '湿度': { en: 'Humidity', fr: 'Humidité', ru: 'Влажность', es: 'Humedad', ar: 'الرطوبة' }, '噪音': { en: 'Noise Level', fr: 'Niveau sonore', ru: 'Уровень шума', es: 'Nivel de ruido', ar: 'مستوى الضوضاء' }, + '主要特点': { en: 'Main Features', fr: 'Caractéristiques principales', ru: 'Основные характеристики', es: 'Características principales', ar: 'الميزات الرئيسية' }, '相数': { en: 'Phase', fr: 'Phase', ru: 'Фаза', es: 'Fase', ar: 'الطور' }, '输入功率因数': { en: 'Input Power Factor', fr: "Facteur de puissance d'entrée", ru: 'Входной коэффициент мощности', es: 'Factor de potencia de entrada', ar: 'معامل قدرة الدخل' }, '转换时间(市电到电池)': { en: 'Transfer Time (AC to Battery)', fr: 'Temps de transfert (secteur vers batterie)', ru: 'Время переключения (сеть на батарею)', es: 'Tiempo de transferencia (red a batería)', ar: 'وقت التحويل (من الشبكة إلى البطارية)' }, @@ -525,12 +526,26 @@ function containsChinese(str) { return /[\u4e00-\u9fa5]/.test(String(str || '')); } +// 机型改名:老数据存的是"标机"/"长机",新数据统一存"标准机型"/"长延时机型"——这里做一层 +// 兼容转换,不管产品是什么时候建的,显示的时候都会自动变成新名字,不需要跑数据迁移。 +function normalizeMachineVariant(v) { + if (v === '标机') return '标准机型'; + if (v === '长机') return '长延时机型'; + return v; +} +// 机型旁边固定带的英文注释,说明有没有内置电池——这个注释始终保持英文,不随导出语言翻译 +// (类似行业里通用的技术标注惯例) +const MACHINE_VARIANT_NOTES = { + '标准机型': '(*with built-in battery)', + '长延时机型': '(*external battery pack required)', +}; + function gatherExportTerms(p, categories) { const cat = (categories || []).find(c => c.id === p.category); const terms = new Set(['产品规格书', '* 产品规格如有变动,恕不另行通知', '型号', '品牌', '产品大类']); if (cat) terms.add(cat.name); if (p.type) { terms.add('细分品类'); terms.add(p.type); } - if (isUpsCategory(cat) && p.machineVariant) { terms.add('机型'); terms.add(p.machineVariant); } + if (isUpsCategory(cat) && p.machineVariant) { terms.add('机型'); terms.add(normalizeMachineVariant(p.machineVariant)); } const rows = productExportRows(p, categories); let hasPortRow = false; rows.forEach(r => { @@ -542,7 +557,9 @@ function gatherExportTerms(p, categories) { }); if (hasPortRow) terms.add('输出'); // 对客卖点/主要特点:每行一条,都是要给客户看的文字,需要翻译 - (p.sellingPoints || '').split('\n').map(l => l.trim()).filter(Boolean).forEach(line => terms.add(line)); + const sellingPointLines = (p.sellingPoints || '').split('\n').map(l => l.trim()).filter(Boolean); + if (sellingPointLines.length > 0) terms.add('主要特点'); + sellingPointLines.forEach(line => terms.add(line)); // 注意:备注(notes)字段故意不收集、不导出——这里经常被用来记内部信息(成本价、 // 采购渠道、库存数量这类),一旦跟着PDF发给客户后果很麻烦,所以规格书导出永远不碰这个字段。 return Array.from(terms); @@ -948,9 +965,9 @@ function normalizeProductTaxonomy(products, categories) { if (p.type && !cat.types.includes(p.type)) cat.types.push(p.type); }); - // 老数据兼容:如果之前版本把"标机/长机"误存进了某个类目的 types 列表里,这里清掉, - // 现在标机/长机是独立字段(machineVariant),不再作为细分品类的一个选项。 - cats.forEach(c => { c.types = c.types.filter(t => t !== '标机' && t !== '长机'); }); + // 老数据兼容:如果之前版本把"标机/长机"(或者新命名"标准机型/长延时机型")误存进了某个类目的 + // types 列表里,这里清掉,机型是独立字段(machineVariant),不再作为细分品类的一个选项。 + cats.forEach(c => { c.types = c.types.filter(t => !['标机', '长机', '标准机型', '长延时机型'].includes(t)); }); return { products: migratedProducts, categories: cats }; } @@ -1341,14 +1358,14 @@ const Styles = () => ( .spec-sheet-table { margin-top: 0; border: 1px solid var(--copper-soft); border-top: none; } .spec-sheet-table td.spec-label { background: #F7F1E8; font-weight: 600; width: 38%; color: #6b5a3e; } .spec-sheet-table tr:nth-child(even) td { background: #FBF8F3; } - .spec-sheet-two-col { display: flex; gap: 32px; align-items: flex-start; } - .spec-sheet-left { flex: 0 0 240px; } - .spec-sheet-right { flex: 1; min-width: 0; } - .spec-sheet-image { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 6px; border: 1px solid var(--copper-soft); } - .spec-sheet-points { margin: 14px 0 0; padding-left: 18px; font-size: 12.5px; color: #444; line-height: 1.7; } + .spec-sheet-cover { text-align: center; padding: 20px 0 8px; } + .spec-sheet-cover .spec-sheet-bar { text-align: left; } + .spec-sheet-image-full { max-width: 70%; max-height: 320px; object-fit: contain; border-radius: 6px; } + .spec-sheet-points { margin: 14px 0 0; padding-left: 18px; font-size: 12.5px; color: #444; line-height: 1.7; text-align: left; } .spec-sheet-points li { margin-bottom: 4px; } - [dir="rtl"] .spec-sheet-two-col { flex-direction: row-reverse; } - [dir="rtl"] .spec-sheet-points { padding-left: 0; padding-right: 18px; } + .spec-sheet-page-break { break-before: page; page-break-before: always; } + [dir="rtl"] .spec-sheet-points { padding-left: 0; padding-right: 18px; text-align: right; } + [dir="rtl"] .spec-sheet-cover .spec-sheet-bar { text-align: right; } @media print { @@ -2045,6 +2062,7 @@ function MainApp({ username, onLogout }) { image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }, + pagebreak: { mode: ['css', 'legacy'], before: '.spec-sheet-page-break' }, }).from(el).save(); } catch (e) { notify('导出PDF失败,请重试'); @@ -3857,7 +3875,7 @@ function ProductForm({ draft, setDraft, onSave, onCancel, categories, onManage, if (newType === '迷你UPS') { set({ type: newType, - machineVariant: '标机', + machineVariant: '标准机型', voltage: draft.voltage || '100~240Vac/50-60Hz', outputPortCount: draft.outputPortCount || '5', outputPortTypes: (draft.outputPortTypes && draft.outputPortTypes.length) ? draft.outputPortTypes : [...DEFAULT_MINI_UPS_OUTPUT_TYPES], @@ -3878,12 +3896,12 @@ function ProductForm({ draft, setDraft, onSave, onCancel, categories, onManage,
机型 {isMiniUps ? ( - + ) : ( )}
@@ -4127,7 +4145,7 @@ function ProductDetail({ product: p, onEdit, onDelete, categories, exportLang, o
} label="分类" value={categoryBreadcrumb(p, categories)} /> } label="品牌" value={p.brand || '—'} /> - {showMachineVariant && } label="机型" value={p.machineVariant} />} + {showMachineVariant && } label="机型" value={p.machineVariant ? `${normalizeMachineVariant(p.machineVariant)} ${MACHINE_VARIANT_NOTES[normalizeMachineVariant(p.machineVariant)] || ''}`.trim() : '—'} />} {powerInfoItems(p, categories).map(item => ( } label={item.label} value={item.value} /> ))} @@ -4156,6 +4174,7 @@ function ProductDetail({ product: p, onEdit, onDelete, categories, exportLang, o
{(() => { const sellingPointLines = (p.sellingPoints || '').split('\n').map(l => l.trim()).filter(Boolean); + const hasCoverPage = !!(p.productImageFileName || sellingPointLines.length > 0); const specTable = ( @@ -4163,7 +4182,7 @@ function ProductDetail({ product: p, onEdit, onDelete, categories, exportLang, o {p.brand && } {p.type && } - {showMachineVariant && } + {showMachineVariant && } {exportRows.map((r, i) => ( @@ -4175,25 +4194,29 @@ function ProductDetail({ product: p, onEdit, onDelete, categories, exportLang, o ); const disclaimer =
{L('* 产品规格如有变动,恕不另行通知')}
; - if (p.productImageFileName) { + if (hasCoverPage) { return ( -
-
- {p.name -
{p.name || '—'}
- {p.brand &&
{p.brand}
} + <> +
+ {p.productImageFileName && ( + {p.name + )} +
{p.name || '—'}
+ {p.brand &&
{p.brand}
} {sellingPointLines.length > 0 && ( -
    - {sellingPointLines.map((line, i) =>
  • {L(line)}
  • )} -
+ <> +
{L('主要特点')}
+
    + {sellingPointLines.map((line, i) =>
  • {L(line)}
  • )} +
+ )}
-
-
{L('产品规格书')}
- {specTable} - {disclaimer} -
-
+
+
{L('产品规格书')}
+ {specTable} + {disclaimer} + ); } return ( @@ -4202,11 +4225,6 @@ function ProductDetail({ product: p, onEdit, onDelete, categories, exportLang, o
{p.name || '—'}
{p.brand &&
{p.brand}
}
- {sellingPointLines.length > 0 && ( -
    - {sellingPointLines.map((line, i) =>
  • {L(line)}
  • )} -
- )}
{L('产品规格书')}
{specTable} {disclaimer}
{L('品牌')}{p.brand}
{L('产品大类')}{cat ? L(cat.name) : '—'}
{L('细分品类')}{L(p.type)}
{L('机型')}{L(p.machineVariant)}
{L('机型')}{`${L(normalizeMachineVariant(p.machineVariant))} ${MACHINE_VARIANT_NOTES[normalizeMachineVariant(p.machineVariant)] || ''}`.trim()}
{r.labelKey === '__port__' ? `${r.portType} ${L('输出')}` : L(r.labelKey)}