feat: 实现前端管理看板与移动端扫码视图组件
This commit is contained in:
84
frontend/src/pages/ScanPage.tsx
Normal file
84
frontend/src/pages/ScanPage.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
/** 扫码干活页 — 编排摄像头扫码 + 手动输入 + 查询结果 */
|
||||
import { useState, useCallback } from "react";
|
||||
import { QrCode } from "lucide-react";
|
||||
import { scanProduct } from "../services/productApi";
|
||||
import type { ProductScanResponse } from "../types/api";
|
||||
import CameraScanner from "../components/scan/CameraScanner";
|
||||
import ManualInput from "../components/scan/ManualInput";
|
||||
import QueryResult from "../components/scan/QueryResult";
|
||||
|
||||
export default function ScanPage() {
|
||||
const [cameraActive, setCameraActive] = useState(false);
|
||||
const [cameraError, setCameraError] = useState<string | null>(null);
|
||||
const [serialNumber, setSerialNumber] = useState("");
|
||||
const [lastScanned, setLastScanned] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [product, setProduct] = useState<ProductScanResponse | null>(null);
|
||||
|
||||
/** 通用查询 */
|
||||
const doQuery = useCallback(async (sn: string) => {
|
||||
if (sn.length < 8) { setError("序列号至少需要 8 位"); return; }
|
||||
setSerialNumber(sn);
|
||||
setLastScanned(sn);
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setProduct(null);
|
||||
try {
|
||||
setProduct(await scanProduct(sn));
|
||||
} catch (err: unknown) {
|
||||
const detail =
|
||||
err && typeof err === "object" && "response" in err
|
||||
? (err as { response?: { data?: { detail?: string } } }).response?.data?.detail
|
||||
: null;
|
||||
setError(detail ?? "网络错误,请检查后端服务");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/** 扫码回调:提取纯序列号 */
|
||||
const handleScan = useCallback(
|
||||
(decodedText: string) => {
|
||||
setCameraError(null);
|
||||
doQuery(decodedText.trim().replace(/[^a-zA-Z0-9]/g, "").slice(0, 16));
|
||||
},
|
||||
[doQuery]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col pb-20 pt-safe">
|
||||
<div className="flex items-center gap-2 px-4 pt-2">
|
||||
<QrCode className="h-5 w-5 text-blue-600" />
|
||||
<h2 className="text-lg font-bold text-gray-800">扫码干活</h2>
|
||||
</div>
|
||||
|
||||
{/* 摄像头扫码 */}
|
||||
<div className="mt-3 px-4">
|
||||
<CameraScanner
|
||||
active={cameraActive}
|
||||
onToggle={setCameraActive}
|
||||
onScan={handleScan}
|
||||
error={cameraError}
|
||||
onError={setCameraError}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 手动输入 */}
|
||||
<div className="mt-3 px-4">
|
||||
<ManualInput
|
||||
value={serialNumber}
|
||||
onChange={setSerialNumber}
|
||||
onSearch={() => doQuery(serialNumber.trim())}
|
||||
loading={loading}
|
||||
lastScanned={lastScanned}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 查询结果 */}
|
||||
<div className="mt-3 px-4">
|
||||
<QueryResult loading={loading} error={error} product={product} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user