Fosi Fossil Service
Small teams. Open source. Built on Fossil.

IaC / SwiftFlow

High-performance WMS with wave picking, smart routing, and real-time inventory visibility.

Public | Owner: IaC | Default branch: trunk | Last update: 2026-10-23 14:37

Artifact 274db23096cf88433366e2a4d829b22c95d15b40ee34f67c29b4292c57379adb:


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>
  )
}

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/IaC/SwiftFlow

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.