163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
|
|
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>
|
|||
|
|
);
|
|||
|
|
}
|