feat(frontend): 管理端页面 — 任务全景树/创建产品/标签打印/认证守卫
App.tsx: - AntApp + AuthProvider + ToastProvider 三层包裹 - /admin/login 独立登录页 - /admin/* 路由认证守卫 AdminLayout: - 未登录→跳转登录; 顶部栏显示用户名+登出 - 侧边栏: 全局概览/产品管理/任务全景 CreateProductDialog (Ant Design 重写): - MOM物料手风琴选择器 (Collapse+Table) - 分组懒加载 + useRef缓存 + 防抖搜索 - HEX ID只读展示 + 序列号/订单号选填 AdminProductsPage: - 打印预览弹窗 (Base64标签预览 + 份数选择) - 打印机设置入口 → AdminPrintConfigPage 扫码组件对齐: - ProductCard: material_name/spec_model/current_location - TaskListCard: 递归 task_tree + 状态配色 + 返工/裂变标签 - QueryResult: task_tree 优先 - ScanPage: 宏观状态栏
This commit is contained in:
162
frontend/src/pages/admin/AdminPrintConfigPage.tsx
Normal file
162
frontend/src/pages/admin/AdminPrintConfigPage.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Settings, Save, Loader2, Wifi, WifiOff } from "lucide-react";
|
||||
import {
|
||||
getPrinterConfig,
|
||||
updatePrinterConfig,
|
||||
type PrinterConfig,
|
||||
} from "../../services/printApi";
|
||||
import { useToast } from "../../components/ui/Toast";
|
||||
|
||||
export default function AdminPrintConfigPage() {
|
||||
const { toast } = useToast();
|
||||
|
||||
const [config, setConfig] = useState<PrinterConfig | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 表单字段
|
||||
const [ip, setIp] = useState("");
|
||||
const [port, setPort] = useState(9100);
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadConfig();
|
||||
}, []);
|
||||
|
||||
async function loadConfig() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const cfg = await getPrinterConfig();
|
||||
setConfig(cfg);
|
||||
const lp = cfg.label_printer;
|
||||
setIp(lp.ip);
|
||||
setPort(lp.port);
|
||||
setEnabled(lp.enabled ?? true);
|
||||
} catch (err: any) {
|
||||
toast(err?.response?.data?.detail ?? "加载配置失败", "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!ip.trim()) {
|
||||
toast("请输入打印机 IP 地址", "error");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const result = await updatePrinterConfig(ip.trim(), port, enabled);
|
||||
toast(result.message, "success");
|
||||
} catch (err: any) {
|
||||
toast(err?.response?.data?.detail ?? "保存失败", "error");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-blue-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-bold text-gray-800">打印机设置</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
配置标签打印机(热敏打标机)的 IP 地址和端口,协议: TSPL
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-md rounded-xl bg-white p-6 shadow-sm">
|
||||
<form onSubmit={handleSave} className="space-y-4">
|
||||
{/* IP 地址 */}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
打印机 IP 地址
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ip}
|
||||
onChange={(e) => setIp(e.target.value)}
|
||||
placeholder="如 192.168.9.221"
|
||||
className="w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm font-mono focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 端口 */}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
端口
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={port}
|
||||
onChange={(e) => setPort(Number(e.target.value))}
|
||||
min={1}
|
||||
max={65535}
|
||||
className="w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm font-mono focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
|
||||
/>
|
||||
<p className="mt-0.5 text-xs text-gray-400">
|
||||
热敏打印机通常使用 9100 端口(Raw Socket)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 启用状态 */}
|
||||
<label className="flex items-center gap-3 cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{enabled ? (
|
||||
<Wifi className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<WifiOff className="h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
<span className="text-sm text-gray-700">
|
||||
{enabled ? "已启用" : "已禁用"}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{/* 保存 */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saving}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{saving ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Save className="h-4 w-4" />
|
||||
)}
|
||||
保存设置
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* 当前状态 */}
|
||||
{config?.label_printer && (
|
||||
<div className="mt-4 rounded-lg bg-gray-50 px-3 py-2.5 text-xs text-gray-500">
|
||||
<p>
|
||||
当前配置: {config.label_printer.ip}:{config.label_printer.port}
|
||||
{" · "}
|
||||
{config.label_printer.enabled ? (
|
||||
<span className="text-green-600 font-medium">已启用</span>
|
||||
) : (
|
||||
<span className="text-gray-400">已禁用</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user