feat: 宏观状态只同步主线+删除5s倒计时+序列号确认+分组切换(订单/设备)+序列号列
This commit is contained in:
@ -35,9 +35,12 @@ export default function AdminProductsPage() {
|
||||
const [editExternalSerial, setEditExternalSerial] = useState("");
|
||||
const [editSaving, setEditSaving] = useState(false);
|
||||
|
||||
// 删除确认
|
||||
// 删除确认 + 防呆
|
||||
const [deleteTarget, setDeleteTarget] = useState<ProductResponse | null>(null);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [countdown, setCountdown] = useState(5);
|
||||
const [serialInput, setSerialInput] = useState("");
|
||||
const canConfirmDelete = countdown <= 0 && serialInput === deleteTarget?.serial_number;
|
||||
|
||||
async function loadProducts() {
|
||||
setLoading(true);
|
||||
@ -118,6 +121,14 @@ export default function AdminProductsPage() {
|
||||
}
|
||||
|
||||
// ---- 删除 ----
|
||||
// 🔧 倒计时:打开删除弹窗时启动
|
||||
useEffect(() => {
|
||||
if (!deleteTarget) { setCountdown(5); setSerialInput(""); return; }
|
||||
setCountdown(5); setSerialInput("");
|
||||
const timer = setInterval(() => setCountdown(c => { if (c <= 1) { clearInterval(timer); return 0; } return c - 1; }), 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, [deleteTarget?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
function confirmDelete(product: ProductResponse) {
|
||||
setDeleteTarget(product);
|
||||
}
|
||||
@ -277,6 +288,7 @@ export default function AdminProductsPage() {
|
||||
)}
|
||||
|
||||
{/* 删除确认弹窗 */}
|
||||
{/* 🔧 删除确认 — 5秒倒计时 + 序列号二次确认 */}
|
||||
{deleteTarget && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={() => !deleting && setDeleteTarget(null)} />
|
||||
@ -286,21 +298,39 @@ export default function AdminProductsPage() {
|
||||
<AlertTriangle className="h-5 w-5 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-gray-800">确认删除</h3>
|
||||
<p className="text-xs text-gray-500">此操作不可恢复</p>
|
||||
<h3 className="text-base font-bold text-gray-800">危险操作</h3>
|
||||
<p className="text-xs text-gray-500">删除产品及其全部关联数据</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mb-2 text-sm text-gray-600">
|
||||
确定要删除产品 <span className="font-mono font-bold">{deleteTarget.serial_number}</span> 吗?
|
||||
<p className="mb-4 text-sm text-gray-600">
|
||||
将永久删除 <span className="font-mono font-bold">{deleteTarget.serial_number}</span> 及其所有任务、记录和二维码。此操作不可恢复。
|
||||
</p>
|
||||
<p className="mb-6 text-xs text-red-500">将同时删除该产品关联的所有任务和二维码。</p>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<button onClick={() => setDeleteTarget(null)} disabled={deleting} className="rounded-lg border border-gray-200 px-4 py-2 text-sm text-gray-600 hover:bg-gray-50 disabled:opacity-50">取消</button>
|
||||
<button onClick={handleDelete} disabled={deleting} className="flex items-center gap-1.5 rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-50">
|
||||
{deleting && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
||||
<Trash2 className="h-3.5 w-3.5" />确认删除
|
||||
{/* 步骤1:输入序列号确认 */}
|
||||
<div className="mb-4">
|
||||
<label className="mb-1 block text-xs font-medium text-gray-600">
|
||||
请输入产品身份证以确认删除
|
||||
</label>
|
||||
<input
|
||||
value={serialInput}
|
||||
onChange={(e) => setSerialInput(e.target.value)}
|
||||
placeholder={deleteTarget.serial_number}
|
||||
maxLength={16}
|
||||
className="w-full rounded-lg border border-gray-200 px-3 py-2 font-mono text-sm focus:border-red-400 focus:outline-none focus:ring-2 focus:ring-red-100"
|
||||
disabled={deleting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 步骤2:5秒倒计时 + 确认按钮 */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button onClick={() => setDeleteTarget(null)} disabled={deleting}
|
||||
className="rounded-lg border border-gray-200 px-4 py-2 text-sm text-gray-600 hover:bg-gray-50 disabled:opacity-50">取消</button>
|
||||
<button onClick={handleDelete} disabled={!canConfirmDelete || deleting}
|
||||
className="flex items-center gap-1.5 rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white transition-all hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-50">
|
||||
{deleting ? <><Loader2 className="h-3.5 w-3.5 animate-spin" />删除中...</>
|
||||
: canConfirmDelete ? <><Trash2 className="h-3.5 w-3.5" />确认删除</>
|
||||
: <>确认删除 ({countdown}s)</>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user