From c098b53f498429e04e5871cfb005f7ae2cdb96a0 Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 10 Apr 2026 16:23:06 +0800 Subject: [PATCH] feat(web): ERP-style form layout prototype (Sales Order) --- web/src/i18n/messages.ts | 34 ++++++++++++++++++++++++++++++++++ web/src/pages/CreateSalesOrderPage.tsx | 351 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------- 2 files changed, 274 insertions(+), 111 deletions(-) diff --git a/web/src/i18n/messages.ts b/web/src/i18n/messages.ts index 00af7ef..f8f8e71 100644 --- a/web/src/i18n/messages.ts +++ b/web/src/i18n/messages.ts @@ -142,6 +142,23 @@ export const en = { 'label.enabled': 'Enabled', 'label.conditionLogic': 'Logic', 'action.newRule': 'New Rule', + + // ─── ERP form ───────────────────────────────────────────── + 'page.salesOrder.create.title': 'New Sales Order', + 'label.orderCode': 'Order Code', + 'label.orderDate': 'Order Date', + 'label.customer': 'Customer', + 'label.createdBy': 'Created By', + 'label.createdAt': 'Created At', + 'label.autoGenerated': 'Auto-generated', + 'tab.orderLines': 'Order Lines', + 'label.item': 'Item', + 'label.lineTotal': 'Line Total', + 'label.selectItem': 'Select item...', + 'label.orderTotal': 'Order Total', + 'label.customFields': 'Custom Fields', + 'action.addRow': 'Add Row', + 'action.removeRow': 'Remove', } as const export const zhCN: Record = { @@ -275,6 +292,23 @@ export const zhCN: Record = { 'label.enabled': '启用', 'label.conditionLogic': '逻辑', 'action.newRule': '新建规则', + + // ─── ERP 表单 ───────────────────────────────────────────── + 'page.salesOrder.create.title': '新建销售订单', + 'label.orderCode': '订单编码', + 'label.orderDate': '订单日期', + 'label.customer': '客户', + 'label.createdBy': '创建人', + 'label.createdAt': '创建时间', + 'label.autoGenerated': '自动生成', + 'tab.orderLines': '订单明细', + 'label.item': '物料', + 'label.lineTotal': '行合计', + 'label.selectItem': '请选择物料...', + 'label.orderTotal': '订单合计', + 'label.customFields': '自定义字段', + 'action.addRow': '添加行', + 'action.removeRow': '删除', } export const locales = { diff --git a/web/src/pages/CreateSalesOrderPage.tsx b/web/src/pages/CreateSalesOrderPage.tsx index bdbe7cc..35281b4 100644 --- a/web/src/pages/CreateSalesOrderPage.tsx +++ b/web/src/pages/CreateSalesOrderPage.tsx @@ -2,9 +2,9 @@ import { useEffect, useState, type FormEvent } from 'react' import { useNavigate } from 'react-router-dom' import { catalog, partners, salesOrders } from '@/api/client' import type { Item, Partner } from '@/types/api' -import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' interface LineInput { itemCode: string @@ -14,6 +14,7 @@ interface LineInput { export function CreateSalesOrderPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [partnerCode, setPartnerCode] = useState('') const [currencyCode] = useState('USD') @@ -49,6 +50,11 @@ export function CreateSalesOrderPage() { setLines(next) } + const orderTotal = lines.reduce( + (sum, l) => sum + Number(l.quantity || 0) * Number(l.unitPrice || 0), + 0, + ) + const onSubmit = async (e: FormEvent) => { e.preventDefault() setError(null) @@ -77,127 +83,250 @@ export function CreateSalesOrderPage() { } } + /* ── label cell (light blue bg, right-aligned) ── */ + const labelCls = + 'bg-sky-50 border-b border-r border-slate-200 px-3 py-2 text-right text-slate-600 whitespace-nowrap text-sm' + /* ── value cell ── */ + const valueCls = 'border-b border-slate-200 px-2 py-1' + const valueClsR = `${valueCls} border-r` // value cell with right border (cols 1 & 3) + /* ── inline input ── */ + const inputCls = 'w-full border-0 outline-none text-sm py-0.5 bg-transparent' + return ( -
- navigate('/sales-orders')}> - Cancel - - } - /> -
-
-
- - setCode(e.target.value)} - placeholder="SO-2026-0003" - className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-brand-500 focus:ring-brand-500" - /> -
-
- - -
-
- - + + {/* ════════ Title bar ════════ */} +
+

+ {t('page.salesOrder.create.title')} +

+
+ + {/* ════════ Top toolbar ════════ */} +
+ +
+ +
+ + {/* ════════ Scrollable body ════════ */} +
+ + {/* ════════ Form header — dense grid ════════ */} +
+
+ {/* Row 1 */} +
+ * {t('label.orderCode')} +
+
+ setCode(e.target.value)} + placeholder="SO-2026-0003" + className={inputCls} + /> +
+
+ {t('label.orderDate')} +
+
+ +
+ + {/* Row 2 */} +
+ * {t('label.customer')} +
+
+ +
+
+ {t('label.currency')} +
+
+ {currencyCode} +
+ + {/* Row 3 — system / auto fields */} +
+ {t('label.createdBy')} +
+
+ {t('label.autoGenerated')} +
+
+ {t('label.createdAt')} +
+
+ {t('label.autoGenerated')} +
-
-
- - -
-
- {lines.map((line, idx) => ( -
- {idx + 1} - - updateLine(idx, 'quantity', e.target.value)} - className="w-20 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-right" - /> - updateLine(idx, 'unitPrice', e.target.value)} - className="w-24 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-right" - /> - -
- ))} -
+ {/* ════════ Tabs ════════ */} +
+
+ {/* ════════ Line items — spreadsheet table ════════ */} +
+ + + + + + + + + + + + + {lines.map((line, idx) => ( + + + + + + + + + ))} + + + + + + + + +
# + * {t('label.item')} + + * {t('label.quantity')} + + * {t('label.unitPrice')} + + {t('label.lineTotal')} + {t('label.actions')}
+ {idx + 1} + + + + updateLine(idx, 'quantity', e.target.value)} + className="w-full border-0 outline-none text-sm py-1 text-right bg-transparent" + /> + + updateLine(idx, 'unitPrice', e.target.value)} + className="w-full border-0 outline-none text-sm py-1 text-right bg-transparent" + /> + + {(Number(line.quantity || 0) * Number(line.unitPrice || 0)).toFixed(2)} + + + +
+ + + {t('label.orderTotal')} + + {orderTotal.toFixed(2)} + +
+
+ + {/* ════════ Custom fields (Tier 1 ext) ════════ */} setExt((prev) => ({ ...prev, [k]: v }))} /> - {error && } - -
- - - After creation, confirm the order to auto-generate work orders. - -
- -
+ {/* ════════ Error ════════ */} + {error && ( +
+ +
+ )} +
+ ) } -- libgit2 0.22.2