|
|
@@ -1,5 +1,5 @@
|
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
|
-import { Modal, message } from 'antd';
|
|
|
+import { Modal, message, Button } from 'antd';
|
|
|
import { Clock } from 'lucide-react';
|
|
|
import { slotApi, LockCabinetSlotVO, SlotPageParam } from '../../api/lockCabinet/slots';
|
|
|
import { lockCabinetApi, LockCabinetVO } from '../../api/lockCabinet';
|
|
|
@@ -20,11 +20,16 @@ export default function MapData({ cabinetId }: MapDataProps) {
|
|
|
const [updateTime, setUpdateTime] = useState<string | null>(null);
|
|
|
const [dialogVisible, setDialogVisible] = useState(false);
|
|
|
const [errorInfo, setErrorInfo] = useState('');
|
|
|
+
|
|
|
+ // 仓位详情弹框:钥匙仓/锁仓点击展示
|
|
|
+ const [slotDetailVisible, setSlotDetailVisible] = useState(false);
|
|
|
+ const [slotDetailType, setSlotDetailType] = useState<'key' | 'lock'>('key');
|
|
|
+ const [slotDetailNo, setSlotDetailNo] = useState(1);
|
|
|
|
|
|
// 3D 控制状态
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
|
const previousMousePosition = useRef({ x: 0, y: 0 });
|
|
|
- const rotation = useRef({ x: -5, y: -25 });
|
|
|
+ const rotation = useRef({ x: 0, y: 0 });
|
|
|
// 用户缩放(在自适应 fitScale 的基础上)
|
|
|
const userScale = useRef(1);
|
|
|
const [fitScale, setFitScale] = useState(1);
|
|
|
@@ -247,6 +252,12 @@ export default function MapData({ cabinetId }: MapDataProps) {
|
|
|
return rows;
|
|
|
}, [slotData]);
|
|
|
|
|
|
+ const openSlotDetail = (type: 'key' | 'lock', slotNo: number) => {
|
|
|
+ setSlotDetailType(type);
|
|
|
+ setSlotDetailNo(slotNo);
|
|
|
+ setSlotDetailVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
// 从机柜详情 remark 解析钥匙/锁仓状态(keys/locks,slotNo,status: online/offline),用于 3D 仓位渲染
|
|
|
const remarkSlots = React.useMemo(() => {
|
|
|
const keyOnline: Record<number, boolean> = {};
|
|
|
@@ -424,7 +435,14 @@ export default function MapData({ cabinetId }: MapDataProps) {
|
|
|
: (slot?.status === '1' || slot?.isOccupied === '1' ? 'lc3dKeyIndicatorRed' : 'lc3dKeyIndicatorGreen');
|
|
|
|
|
|
return (
|
|
|
- <div key={idx} className="lc3dKeySlot">
|
|
|
+ <div
|
|
|
+ key={idx}
|
|
|
+ className="lc3dKeySlot lc3dSlotClickable"
|
|
|
+ role="button"
|
|
|
+ tabIndex={0}
|
|
|
+ onMouseDown={(e) => e.stopPropagation()}
|
|
|
+ onClick={() => openSlotDetail('key', slotNo)}
|
|
|
+ >
|
|
|
<span className="lc3dKeyNumber">{slotNo}</span>
|
|
|
{isOnlineFromRemark && (
|
|
|
<img src={keyImg} alt="钥匙" className="lc3dSlotHardwareImg" style={{ width: 30, height: 30 }} title="在线" />
|
|
|
@@ -450,7 +468,14 @@ export default function MapData({ cabinetId }: MapDataProps) {
|
|
|
? (isOnlineFromRemark ? 'lc3dLockIndicatorRed' : 'lc3dLockIndicatorGreen')
|
|
|
: (slot?.status === '1' || slot?.isOccupied === '1' ? 'lc3dLockIndicatorRed' : 'lc3dLockIndicatorOff');
|
|
|
return (
|
|
|
- <div key={slotIdx} className="lc3dLockSlot">
|
|
|
+ <div
|
|
|
+ key={slotIdx}
|
|
|
+ className="lc3dLockSlot lc3dSlotClickable"
|
|
|
+ role="button"
|
|
|
+ tabIndex={0}
|
|
|
+ onMouseDown={(e) => e.stopPropagation()}
|
|
|
+ onClick={() => openSlotDetail('lock', num)}
|
|
|
+ >
|
|
|
<span className="lc3dLockNumber">{num}</span>
|
|
|
{isOnlineFromRemark && (
|
|
|
<img src={lockImg} alt="锁" className="lc3dSlotHardwareLockImg" style={{ width: 30, height: 20 }} title="在线" />
|
|
|
@@ -605,6 +630,59 @@ export default function MapData({ cabinetId }: MapDataProps) {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ {/* 仓位详情弹框:钥匙仓/锁仓点击展示,参考挂锁详情样式 */}
|
|
|
+ <Modal
|
|
|
+ title={slotDetailType === 'key' ? `钥匙-${slotDetailNo} - 详情` : `挂锁-${slotDetailNo} - 详情`}
|
|
|
+ open={slotDetailVisible}
|
|
|
+ onCancel={() => setSlotDetailVisible(false)}
|
|
|
+ footer={[
|
|
|
+ <Button key="edit" type="primary" onClick={() => { message.info('编辑功能待接入'); }}>
|
|
|
+ 编辑
|
|
|
+ </Button>,
|
|
|
+ <Button key="delete" danger onClick={() => { message.info('删除功能待接入'); }}>
|
|
|
+ 删除
|
|
|
+ </Button>,
|
|
|
+ ]}
|
|
|
+ width={480}
|
|
|
+ destroyOnClose
|
|
|
+ >
|
|
|
+ <div className="pt-2">
|
|
|
+ <div className="text-base font-semibold text-gray-900 mb-4">基本信息</div>
|
|
|
+ <div className="space-y-0 border-t border-gray-100">
|
|
|
+ <div className="flex justify-between items-center py-3 border-b border-gray-100">
|
|
|
+ <span className="text-gray-600">设备名称</span>
|
|
|
+ <span className="text-gray-900 font-medium">
|
|
|
+ {slotDetailType === 'key' ? `钥匙-${slotDetailNo}` : `挂锁-${slotDetailNo}`}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div className="flex justify-between items-center py-3 border-b border-gray-100">
|
|
|
+ <span className="text-gray-600">NFC编号</span>
|
|
|
+ <span className="text-gray-900 font-medium">
|
|
|
+ {slotDetailType === 'key' ? `KEY${String(slotDetailNo).padStart(3, '0')}` : `LOCK${String(slotDetailNo).padStart(3, '0')}`}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div className="flex justify-between items-center py-3 border-b border-gray-100">
|
|
|
+ <span className="text-gray-600">设备类型</span>
|
|
|
+ <span className="text-gray-900 font-medium">{slotDetailType === 'key' ? '钥匙' : '锁具'}</span>
|
|
|
+ </div>
|
|
|
+ <div className="flex justify-between items-center py-3 border-b border-gray-100">
|
|
|
+ <span className="text-gray-600">状态</span>
|
|
|
+ <span className="inline-flex px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-700 border border-green-200">启用</span>
|
|
|
+ </div>
|
|
|
+ <div className="flex justify-between items-center py-3 border-b border-gray-100">
|
|
|
+ <span className="text-gray-600">在线状态</span>
|
|
|
+ <span className={`inline-flex px-3 py-1 rounded-full text-sm font-medium border ${
|
|
|
+ (slotDetailType === 'key' ? remarkSlots.keyOnline[slotDetailNo] : remarkSlots.lockOnline[slotDetailNo])
|
|
|
+ ? 'bg-green-100 text-green-700 border-green-200'
|
|
|
+ : 'bg-gray-100 text-gray-600 border-gray-200'
|
|
|
+ }`}>
|
|
|
+ {(slotDetailType === 'key' ? remarkSlots.keyOnline[slotDetailNo] : remarkSlots.lockOnline[slotDetailNo]) ? '在线' : '离线'}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
{/* 异常信息对话框 */}
|
|
|
<Modal
|
|
|
title="异常信息"
|