Artifact 2ba58e16ad43956b232bc8e980452cabcfeede4eafa6b6060dee339a3c5b6928:
- File
web/src/pages/Orders.tsx
— part of check-in
[dfa747c1ad]
at
2026-10-23 06:08:28
on branch trunk
— Web 管理控制台
- 用 Vite 和 React 做完整的 Web 端:登录、按权限显示菜单、切换当前作业仓库 - 覆盖来肯同步、仓库、库位规划、商品、销售订单、采购入库与批次码、采购订单、库存、用户授权和审计日志 - 开发时由 Vite 把 /api 代理到本机后端 - SQLite 写入和页面读取冲突时自动重试,避免请求进程因数据库忙碌直接崩溃 (user: pluto size: 7018)
import { useEffect, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { api, money, qty } from '../api' import { Empty, Page, Pager } from '../ui' type Summary = { bill_id: number bill_code: string customer_name: string warehouse_name: string bill_date: string status_name: string total_qty: number total_money: number } type Item = { bill_detail_id: number goods_code: string goods_name: string barcode: string base_qty: number base_unit: string price: number total_money: number } type Shipment = { id: string created_at: string lines: { batch_no: string; location_code: string; qty: number; goods_name: string; expires_on: string }[] } export function Orders() { const { billId } = useParams() if (billId) return <OrderDetail billId={billId} /> return <OrderList /> } function OrderList() { const navigate = useNavigate() const [pending, setPending] = useState(true) const [offset, setOffset] = useState(0) const [total, setTotal] = useState(0) const [rows, setRows] = useState<Summary[]>([]) const [error, setError] = useState('') const limit = 50 useEffect(() => { const path = pending ? '/api/orders/pending' : '/api/orders/all' api<{ total: number; orders: Summary[] }>(`${path}?limit=${limit}&offset=${offset}`) .then((data) => { setTotal(data.total) setRows(data.orders) }) .catch((err: unknown) => setError(err instanceof Error ? err.message : '加载失败')) }, [pending, offset]) return ( <Page title="销售订单" extra={ <div className="actions"> <button type="button" className={pending ? '' : 'ghost'} onClick={() => { setPending(true); setOffset(0) }}> 待发货 </button> <button type="button" className={pending ? 'ghost' : ''} onClick={() => { setPending(false); setOffset(0) }}> 全部 </button> </div> } > {error && <p className="banner error">{error}</p>} {rows.length === 0 ? ( <Empty text="没有订单" /> ) : ( <div className="table-wrap"> <table> <thead> <tr> <th>单号</th> <th>客户</th> <th>仓库</th> <th>日期</th> <th>状态</th> <th>数量</th> <th>金额</th> </tr> </thead> <tbody> {rows.map((row) => ( <tr key={row.bill_id} className="clickable" onClick={() => navigate(`/orders/${row.bill_id}`)}> <td><Link to={`/orders/${row.bill_id}`}>{row.bill_code}</Link></td> <td>{row.customer_name}</td> <td>{row.warehouse_name}</td> <td>{row.bill_date}</td> <td>{row.status_name}</td> <td>{qty(row.total_qty)}</td> <td>{money(row.total_money)}</td> </tr> ))} </tbody> </table> </div> )} <Pager total={total} limit={limit} offset={offset} onPage={setOffset} /> </Page> ) } function OrderDetail({ billId }: { billId: string }) { const [order, setOrder] = useState<(Summary & { items: Item[]; remark: string }) | null>(null) const [shipments, setShipments] = useState<Shipment[]>([]) const [error, setError] = useState('') useEffect(() => { api<{ order: Summary & { items: Item[]; remark: string } }>(`/api/orders/detail/${billId}`) .then((data) => setOrder(data.order)) .catch((err: unknown) => setError(err instanceof Error ? err.message : '加载失败')) api<{ shipments: Shipment[] }>(`/api/orders/${billId}/shipments`) .then((data) => setShipments(data.shipments)) .catch((err: unknown) => setError(err instanceof Error ? err.message : '出库记录加载失败')) }, [billId]) return ( <Page title={order ? order.bill_code : '销售订单'}> <Link className="back" to="/orders">返回列表</Link> {error && <p className="banner error">{error}</p>} {order && ( <> <div className="cards"> <div className="card"><span>客户</span><strong>{order.customer_name}</strong></div> <div className="card"><span>仓库</span><strong>{order.warehouse_name}</strong></div> <div className="card"><span>状态</span><strong>{order.status_name}</strong></div> <div className="card"><span>数量 / 金额</span><strong>{qty(order.total_qty)} / {money(order.total_money)}</strong></div> </div> {order.remark && <p className="muted">备注:{order.remark}</p>} <div className="table-wrap"> <table> <thead> <tr> <th>商品</th> <th>编码</th> <th>条码</th> <th>数量</th> <th>单价</th> <th>金额</th> </tr> </thead> <tbody> {order.items.map((item) => ( <tr key={item.bill_detail_id}> <td>{item.goods_name}</td> <td className="mono">{item.goods_code}</td> <td className="mono">{item.barcode}</td> <td>{qty(item.base_qty)} {item.base_unit}</td> <td>{money(item.price)}</td> <td>{money(item.total_money)}</td> </tr> ))} </tbody> </table> </div> <h2>出库记录</h2> {shipments.length === 0 ? ( <Empty text="还没有本地出库" /> ) : ( shipments.map((shipment) => ( <div key={shipment.id} className="panel"> <strong>{shipment.created_at}</strong> <div className="table-wrap"> <table> <thead> <tr> <th>商品</th> <th>批次</th> <th>库位</th> <th>到期</th> <th>数量</th> </tr> </thead> <tbody> {shipment.lines.map((line, index) => ( <tr key={`${line.batch_no}-${index}`}> <td>{line.goods_name}</td> <td className="mono">{line.batch_no}</td> <td className="mono">{line.location_code}</td> <td>{line.expires_on}</td> <td>{qty(line.qty)}</td> </tr> ))} </tbody> </table> </div> </div> )) )} </> )} </Page> ) }