Artifact 274db23096cf88433366e2a4d829b22c95d15b40ee34f67c29b4292c57379adb:
- File
web/src/pages/Shell.tsx
— part of check-in
[bee9efb316]
at
2026-10-23 06:37:04
on branch trunk
— 同一仓库内移库,并留下波次已占用的数量
- 把一个批次从源库位转到启用的目标库位,库存立刻变更 - 未完成波次占用的数量不能移走,撤销波次后才能继续移 - 移库记录留在本地,不写回来肯 (user: pluto size: 3802)
import { useEffect, useState } from 'react' import { NavLink, Outlet } from 'react-router-dom' import { api, can, type Warehouse } from '../api' import { useSession } from '../session' import { Banner } from '../ui' const links = [ { to: '/', label: '概览', end: true }, { to: '/warehouses', label: '仓库', permission: 'warehouse.view' }, { to: '/locations', label: '库位', permission: 'location.view' }, { to: '/products', label: '商品', permission: 'product.view' }, { to: '/orders', label: '销售订单', permission: 'order.view' }, { to: '/waves', label: '波次', permission: 'wave.manage' }, { to: '/receipts', label: '采购入库', permission: 'inbound.view' }, { to: '/inbounds', label: '采购订单', permission: 'inbound.view' }, { to: '/inventory', label: '库存', permission: 'inventory.view' }, { to: '/putaway', label: '上架', permission: 'putaway.execute' }, { to: '/moves', label: '移库', permission: 'move.execute' }, { to: '/users', label: '用户与权限', permission: 'user.manage' }, { to: '/audit', label: '审计日志', permission: 'audit.view' }, ] export function Shell() { const { user, logout, setUser } = useSession() const [warehouses, setWarehouses] = useState<Warehouse[]>([]) const [error, setError] = useState('') useEffect(() => { if (!can(user, 'warehouse.view')) return api<{ warehouses: Warehouse[] }>('/api/warehouses') .then((data) => { setWarehouses(data.warehouses) setError('') }) .catch((err: unknown) => setError(err instanceof Error ? err.message : '仓库加载失败')) }, [user]) const stations = warehouses.filter((row) => row.operational) async function onWarehouse(warehouseId: number) { setError('') try { const data = await api<{ user: NonNullable<typeof user> }>('/api/auth/warehouse', { method: 'POST', body: JSON.stringify({ warehouse_id: warehouseId }), }) setUser(data.user) } catch (err) { setError(err instanceof Error ? err.message : '切换仓库失败') } } return ( <div className="shell"> <aside className="sidebar"> <div className="brand"> <strong>SwiftFlow</strong> <span>仓储管理</span> </div> <nav className="nav"> {links .filter((link) => !link.permission || can(user, link.permission)) .map((link) => ( <NavLink key={link.to} to={link.to} end={link.end}> {link.label} </NavLink> ))} </nav> <div className="spacer" /> <NavLink className="account" to="/account"> 账号 </NavLink> </aside> <div className="main"> <header className="topbar"> <div className="who"> <b>{user?.display_name}</b> <span>{user?.username}</span> </div> <div className="top-actions"> <label className="field"> <span>当前作业仓库</span> <select value={user?.current_warehouse_id || ''} onChange={(event) => onWarehouse(Number(event.target.value))} > <option value="" disabled> 未选择 </option> {stations.map((row) => ( <option key={row.id} value={row.id}> {row.name}({row.code}) </option> ))} </select> </label> <button type="button" className="ghost" onClick={() => void logout()}> 退出 </button> </div> </header> <Banner kind="error" text={error} /> <Outlet /> </div> </div> ) }