Artifact 3afb90cb5bd4a0fc27691983eddb089713831a9d68e9f3d29b16f11d8b6b6231:
- File
web/src/ui.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: 1553)
import type { ReactNode } from 'react' export function Page({ title, extra, children, }: { title: string extra?: ReactNode children: ReactNode }) { return ( <section className="page"> <header className="page-head"> <h1>{title}</h1> {extra} </header> {children} </section> ) } export function Banner({ kind, text }: { kind: 'error' | 'ok'; text: string }) { if (!text) return null return ( <p className={kind === 'error' ? 'banner error' : 'banner ok'} role="alert"> {text} </p> ) } export function Empty({ text }: { text: string }) { return <p className="empty">{text}</p> } export function Pager({ total, limit, offset, onPage, }: { total: number limit: number offset: number onPage: (offset: number) => void }) { const pages = Math.max(1, Math.ceil(total / limit)) const page = Math.floor(offset / limit) + 1 return ( <div className="pager"> <span> 共 {total} 条,第 {page} / {pages} 页 </span> <button type="button" disabled={offset <= 0} onClick={() => onPage(Math.max(0, offset - limit))}> 上一页 </button> <button type="button" disabled={offset + limit >= total} onClick={() => onPage(offset + limit)} > 下一页 </button> </div> ) } export function Field({ label, children, }: { label: string children: ReactNode }) { return ( <label className="field"> <span>{label}</span> {children} </label> ) }