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 18852fc199fb26bbf4a54bbb8741c80e92d214334e481dcfb2c3a80eefaf1e36:


import { useEffect, useState } from 'react'
import { api, can } from '../api'
import { useSession } from '../session'
import { Banner, Empty, Page } from '../ui'

type Run = {
  id: string
  entity: string
  trigger: string
  started_at: string
  finished_at: string
  status: string
  fetched: number
  upserted: number
  error_message: string
}

type Status = {
  credentials_configured: boolean
  ql361_username: string
  session_cached: boolean
  order_poll_seconds: number
  request_gap_ms: number
  stock_warehouses: string[]
  recent_runs: Run[]
}

const syncs = [
  { path: '/api/sync/products', label: '同步商品' },
  { path: '/api/sync/orders', label: '同步销售订单' },
  { path: '/api/sync/inbounds', label: '同步采购订单' },
  { path: '/api/sync/purchase-receipts', label: '同步采购入库单' },
]

export function Dashboard() {
  const { user } = useSession()
  const [status, setStatus] = useState<Status | null>(null)
  const [error, setError] = useState('')
  const [ok, setOk] = useState('')
  const [pending, setPending] = useState('')

  function load() {
    if (!can(user, 'sync.view')) return
    api<Status>('/api/ql361/status')
      .then(setStatus)
      .catch((err: unknown) => setError(err instanceof Error ? err.message : '加载失败'))
  }

  useEffect(load, [user])

  async function run(path: string, label: string) {
    if (!window.confirm(`确定${label}?这会向来肯发起只读查询。`)) return
    setPending(label)
    setError('')
    setOk('')
    try {
      const report = await api<{ fetched: number; upserted: number }>(path, { method: 'POST' })
      setOk(`${label}完成,拉取 ${report.fetched},写入 ${report.upserted}`)
      load()
    } catch (err) {
      setError(err instanceof Error ? err.message : '同步失败')
    } finally {
      setPending('')
    }
  }

  return (
    <Page title="概览">
      <Banner kind="error" text={error} />
      <Banner kind="ok" text={ok} />
      {status ? (
        <div className="cards">
          <div className="card">
            <span>来肯账号</span>
            <strong>{status.credentials_configured ? status.ql361_username : '未配置'}</strong>
          </div>
          <div className="card">
            <span>来肯会话</span>
            <strong>{status.session_cached ? '已缓存' : '未登录'}</strong>
          </div>
          <div className="card">
            <span>单据轮询</span>
            <strong>每 {status.order_poll_seconds} 秒</strong>
          </div>
          <div className="card">
            <span>请求间隔</span>
            <strong>{status.request_gap_ms} 毫秒</strong>
          </div>
          <div className="card">
            <span>采集库存的仓库</span>
            <strong>{status.stock_warehouses.join('、')}</strong>
          </div>
        </div>
      ) : (
        <p className="muted">当前账号不能查看来肯同步状态。</p>
      )}
      {can(user, 'sync.run') && (
        <div className="actions">
          {syncs.map((item) => (
            <button
              key={item.path}
              type="button"
              disabled={pending !== ''}
              onClick={() => void run(item.path, item.label)}
            >
              {pending === item.label ? '同步中…' : item.label}
            </button>
          ))}
        </div>
      )}
      <h2>最近同步</h2>
      {!status || status.recent_runs.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>
              {status.recent_runs.map((run) => (
                <tr key={run.id}>
                  <td>{run.finished_at || run.started_at}</td>
                  <td>{run.entity}</td>
                  <td>{run.trigger}</td>
                  <td>{run.status}</td>
                  <td>{run.fetched}</td>
                  <td>{run.upserted}</td>
                  <td>{run.error_message}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </Page>
  )
}

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.